- 小字:這篇文章探討的僅僅是Vert.x入門,簡(jiǎn)要實(shí)現(xiàn)以及幫助理解
按照我的習(xí)慣先上結(jié)論
生而為了微服務(wù),生而為了高并發(fā),去除了鎖概念的框架,Vertx。因?yàn)橛蠩ventBus的存在打散了傳統(tǒng)開發(fā)層于層之間的關(guān)系,讓層的關(guān)系變得"松散而有序",每個(gè)Vertx實(shí)例都會(huì)維護(hù)一個(gè)隊(duì)列,所以也就沒有了鎖的需要(本來(lái)在現(xiàn)實(shí)中鎖這個(gè)概念就有點(diǎn)不符合邏輯),這大大的提升了效率。
舉一個(gè)不太恰當(dāng)?shù)睦?,以前的開發(fā)就好比有一個(gè)馬廄,里面有一個(gè)水槽,一次只能容納一匹馬喝水,其他的馬想喝水那么就必須排隊(duì)(阻塞)。好了現(xiàn)在有那么幾匹馬不太守規(guī)矩,從寧一邊來(lái)?yè)屵@個(gè)水槽(高并發(fā)),為了阻止這種情況出現(xiàn),我們有了圍欄讓馬兒依次喝水(鎖)。
現(xiàn)在有了Vertx,里面的EvevtBus干了一件事情,把這個(gè)水槽拉得很長(zhǎng),長(zhǎng)到所有的馬都可以喝水,不但馬可以喝水,其他鹿啊,狗啊,貓啊都可以來(lái)喝水,牛逼~
并且Vertx里面還有個(gè)執(zhí)行單元的概念,一個(gè)執(zhí)行單元被注冊(cè)好之后,開啟。如果現(xiàn)在吞吐量到頂了,好了現(xiàn)在騷操作來(lái)了,你可以再開啟一次。吞吐量翻倍,Vertx里里面的x居然是復(fù)數(shù)的意思。
1.
Vert.x簡(jiǎn)介
- JVM上的Reative開發(fā)套件。
Vert.x目前是見過最功能最強(qiáng)大,第三方庫(kù)依賴最少的Java框架,它只依賴Netty4以及Jacskon。 -
Vert.x最大的特點(diǎn)就在于異步(底層基于Netty),通過事件循環(huán)(EventLoop)來(lái)調(diào)起存儲(chǔ)在異步任務(wù)隊(duì)列(CallBackQueue)中的任務(wù),大大降低了傳統(tǒng)阻塞模型中線程對(duì)于操作系統(tǒng)的開銷。因此相比較傳統(tǒng)的阻塞模型,異步模型能夠很大層度的提高系統(tǒng)的并發(fā)量。 -
Vert.x除了異步之外,還提供了非常多的吸引人的技術(shù),比如EventBus,通過EventBus可以非常簡(jiǎn)單的實(shí)現(xiàn)分布式消息,進(jìn)而為分布式系統(tǒng)調(diào)用,微服務(wù)奠定基礎(chǔ)。除此之外,還提供了對(duì)多種客戶端的支持,比如Redis,RabbitMQ,Kafka等等。 - 多語(yǔ)言使用
-
Java版本實(shí)現(xiàn)HTTP服務(wù)
public class MyHttpServer extends AbstractVerticle {
public static void main(String[] args) {
// 創(chuàng)建服務(wù)
MyHttpServer verticle = new MyHttpServer();
Vertx vertx = Vertx.vertx();
// 部署服務(wù),會(huì)執(zhí)行MyHttpServer的start方法
vertx.deployVerticle(verticle);
}
@Override
public void start() throws Exception {
// 在這里可以通過this.vertx獲取到當(dāng)前的Vertx
Vertx vertx = this.vertx;
// 創(chuàng)建一個(gè)HttpServer
HttpServer server = vertx.createHttpServer();
server.requestHandler(request -> {
// 獲取到response對(duì)象
HttpServerResponse response = request.response();
// 設(shè)置響應(yīng)頭
response.putHeader("Content-type", "text/html;charset=utf-8");
// 響應(yīng)數(shù)據(jù)
response.end("SUCCESS");
});
// 指定監(jiān)聽80端口
server.listen(80);
}
}
-
JavaScript實(shí)現(xiàn)HTTP服務(wù)
vertx.createHttpServer()
.requestHandler(function (req) {
req.response()
.putHeader("content-type", "text/plain")
.end("Hello from ``Vert.x``!");
}).listen(8080);
另外還可以使用
Groovy,Ruby,Ceylon,Scala,Kotlin;不依賴中間件
完善的生態(tài)
為微服務(wù)而生
2.
Vert.x能做什么事情?
Java能做的,Vert.x都能做。Node能做的Vert.x也能做
- Web開發(fā),
Vert.x封裝了Web開發(fā)常用的組件,支持路由、Session管理、模板等,可以非常方便的進(jìn)行Web開發(fā)。重要的是不需要容器 - TCP/UDP開發(fā),
Vert.x底層基于Netty,并且進(jìn)行了完善的封裝。 - 原生提供了
WebSocket的支持。 -
EventBus(事件總線)是Vert.x的神經(jīng)系統(tǒng),通過EventBus可以實(shí)現(xiàn)分布式消息,遠(yuǎn)程方法調(diào)用等等。正是因?yàn)镋ventBus的存在,Vert.x可以非常便捷的開發(fā)微服務(wù)應(yīng)用。 - 支持主流的數(shù)據(jù)和消息的訪問
redis,mongodb,rabbitmq,kafka等
3. 什么是異步編程?
對(duì)于習(xí)慣使用springboot的我來(lái)說異步編程是一個(gè)新名詞。異步編程是Vert.x的一大特性,也是Vert.x的核心。可以用Ajax來(lái)做類比。
//$.ajax方法并不會(huì)阻塞,
//而是直接向下執(zhí)行,
//等到遠(yuǎn)程服務(wù)器響應(yīng)之后,才會(huì)回調(diào)success方法,
//那么這時(shí)候success方法才會(huì)執(zhí)行。
//ajax下面的代碼不會(huì)等到success方法執(zhí)行完畢之后再執(zhí)行
console.log("1");
$.ajax({
"url" : "/hello",
"type" : "post",
"dataType" : "json",
"success" : function(val) {
console.log("2");
}
});
console.log("3");
類比Vert.x
//可以看到是極為相似的
System.out.println("1")
WebClient
.create(vertx)
.postAbs(REQUEST_URL) // 這里指定的是請(qǐng)求的地址
.sendBuffer(buffer, res -> { // buffer是請(qǐng)求的數(shù)據(jù)
if (res.succeeded()) {
// 請(qǐng)求遠(yuǎn)程服務(wù)成功
System.out.println("2")
} else {
// 請(qǐng)求失敗
resultHandler.handle(Future.failedFuture("請(qǐng)求服務(wù)器失敗..."));
}
});
System.out.println("3")
4.性能
- 先看圖
[站外圖片上傳中...(image-fa87bd-1560523010556)]
該測(cè)試使用jmeter完成,模擬一秒內(nèi)10000用戶同時(shí)發(fā)出http請(qǐng)求,服務(wù)端返回Json數(shù)據(jù)這樣一個(gè)流程。第一套架構(gòu)是Vert.x家族自己的Vert.x-web框架加上Vert.x-JDBC持久化框架;第二套是Vert.x-web與Spring Boot和JPA兩個(gè)框架整合;第三套是傳統(tǒng)的Spring MVC,Spring Boot和JPA框架。三個(gè)測(cè)試用例均為單http服務(wù)器,前兩者是Vert.x自帶的netty,后者是內(nèi)嵌的Tomcat。數(shù)據(jù)庫(kù)使用的是hsqldb內(nèi)存數(shù)據(jù)庫(kù),連接池是hikariCP,最大連接數(shù)設(shè)置為100。Tomcat的線程池最大連接數(shù)設(shè)置為了1000,可以認(rèn)為是已經(jīng)設(shè)置到最大值了。出錯(cuò)時(shí),報(bào)的錯(cuò)誤均為java.net.ConnectException異常,錯(cuò)誤消息為Connection refused: connect。應(yīng)該是并發(fā)鏈接超過了上限,請(qǐng)求被拒絕了。
從結(jié)果上看,第二套架構(gòu)性能最優(yōu),其吞吐量約為第三套傳統(tǒng)架構(gòu)的3.3倍,錯(cuò)誤率也相對(duì)較低,第一套雖然錯(cuò)誤率高,單吞吐量也大幅超越了傳統(tǒng)架構(gòu)。因此在較為極限的并發(fā)測(cè)試下,Vert.x架構(gòu)的性能較傳統(tǒng)Spring Boot是有明顯優(yōu)勢(shì)的。
測(cè)試框架版本:
Vert.x系列:3.5.1
Spring Boot系列:1.5.10.RELEASE
測(cè)試環(huán)境:
OS:windows 7 64位
CPU:英特爾 至強(qiáng) E3-1231 V3
內(nèi)存:8G
5.思考
Vert.x確實(shí)非常好,把Java8的新特性用得淋漓盡致,而且從上面的性能分析來(lái)看,優(yōu)于傳統(tǒng)框架。
- 但是:異步編程以及面向函數(shù)的思想和以前太不一樣了。還有大量使用
Java8的新特性。比如lambda表達(dá)式,可以簡(jiǎn)化匿名內(nèi)部類的編寫。所以要寫好Vert.x你必須熟悉Java的新特性,不然寫出來(lái)的代碼難以維護(hù)。其次是Vert.x沒有對(duì)關(guān)系型數(shù)據(jù)庫(kù)的ORM支持,在傳統(tǒng)開發(fā)種我們可以選擇Hibernate或者是MyBatits這種ORM框架來(lái)對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作。但Vert.x這方面就比較弱了,但也不是不能用只不過封裝的比較淺??傊褪且痪湓?,東西是好東西,就是用起來(lái)不習(xí)慣尤其是數(shù)據(jù)庫(kù)操作很繁瑣還有大量的匿名內(nèi)部類。
6.整合
終于說到重點(diǎn)了,那就是
springboot處理具體業(yè)務(wù)對(duì)接Vert.x的URL控制,而且上面的性能分析也指出這種模式比純Vert.x還要好一點(diǎn),而且兼顧了傳統(tǒng)開發(fā)的語(yǔ)法,算是一種過渡,并且積極擁抱新技術(shù),對(duì)Java新特性的理解也非常友好。首先建立一個(gè)springboot項(xiàng)目,并且去掉內(nèi)置tomcat,我們使用Vert.x來(lái)接管路由。
新建一個(gè)StaticServer類來(lái)創(chuàng)建一個(gè)http服務(wù)
/**
* 交給springboot管理周期
*/
@Component
public class StaticServer extends AbstractVerticle {
private final AppConfiguration configuration;
private final IServicePrint2Page iServicePrint2Page;
@Autowired
public StaticServer(AppConfiguration configuration, IServicePrint2Page iServicePrint2Page) {
//加載配置文件
this.configuration = configuration;
this.iServicePrint2Page = iServicePrint2Page;
}
@Override
public void start() {
//創(chuàng)建路由對(duì)象
Router router = Router.router(vertx);
//獲取整個(gè)消息體 放進(jìn)RoutingContext
router.route().handler(BodyHandler.create());
//創(chuàng)建http服務(wù)
HttpServer server = vertx.createHttpServer();
//發(fā)送消息
//如果后期添加上mybaitis需要用blockingHandler方法來(lái)執(zhí)行操作
//創(chuàng)建新的線程執(zhí)行(一般用于執(zhí)行阻塞調(diào)用)
router.get("/text").blockingHandler(iServicePrint2Page::sendMsg);
//啟動(dòng)監(jiān)聽
server.requestHandler(router::accept).listen(configuration.httpPort());
}
/**
* 結(jié)束的時(shí)候可以進(jìn)行一些操作
* <p>
* throws Exception
*/
@Override
public void stop() throws Exception {
super.stop();
}
}
- 在SpringBoot啟動(dòng)類上添加initVerticle方法添加@PostConstruct注解來(lái)讓SpringBoot啟動(dòng)的時(shí)候執(zhí)行一次
private final StaticServer staticServer;
/****
* 注入Http服務(wù)類
* @param staticServer
*/
@Autowired
public SptingvertxApplication(StaticServer staticServer) {
this.staticServer = staticServer;
}
/**
* 部署Vertx方法
* <p>
* PostConstruct 修飾的方法會(huì)在服務(wù)器加載的時(shí)候運(yùn)行,并且只會(huì)被服務(wù)器執(zhí)行一次
*/
@PostConstruct
public void initVerticle() {
Vertx.vertx().deployVerticle(staticServer);
}
7.
EventBus
它是Vert.X的核心,在集群中容器之間的通信,各個(gè)Verticle之間的通訊都是經(jīng)過Event Bus來(lái)實(shí)現(xiàn)的
-
概念
- 尋址
消息EventBus發(fā)送到一個(gè)字符串地址上(address)。任意字符串都是合法的。 - 處理器
消息在處理器(Handler)中被接收。您可以在某個(gè)地址上注冊(cè)一個(gè)處理器來(lái)接收消息。
同一個(gè)地址可以注冊(cè)許多不同的處理器,一個(gè)處理器也可以注冊(cè)在多個(gè)不同的地址上。(說人話就是:地址可以重復(fù),并且向這個(gè)重復(fù)的地址發(fā)送消息的時(shí)候,所有用這個(gè)地址注冊(cè)的Handler都會(huì)執(zhí)行) - EventBus發(fā)布消息
消息將被發(fā)布到一個(gè)地址中,發(fā)布意味著會(huì)將信息傳遞給 所有 注冊(cè)在該地址上的處理器。這和 發(fā)布/訂閱模式 很類似。
- 尋址
簡(jiǎn)要實(shí)現(xiàn)
- 向
EventBus發(fā)送一條消息
//喚起一個(gè)時(shí)間總線
EventBus eventBus = routingContext.vertx().eventBus();
//發(fā)布消息
//地址上的所有方法都會(huì)收到消息
//eventBus.send(
//發(fā)送一個(gè)簡(jiǎn)單消息
//如果使用send。就是點(diǎn)對(duì)點(diǎn)模式
//消息將被發(fā)送到一個(gè)地址中,
//Vert.x將會(huì)把消息分發(fā)到某個(gè)注冊(cè)在該地址上的處理器。
//若這個(gè)地址上有不止一個(gè)注冊(cè)過的處理器,
//它將使用 不嚴(yán)格的輪詢算法 選擇其中一個(gè)。
eventBus.send(
// 消息地址
SpringVerticle.GET_HELLO_MSG_SERVICE_ADDRESS,
// 消息內(nèi)容
"測(cè)試", info -> {//異步處理結(jié)果
if (info.succeeded()) {
//訪問成功
response.end(info.result().body().toString());
} else {
//訪問失敗
response.setStatusCode(400).end(info.cause().toString());
}
});
-
EventBus消息提取
//喚起時(shí)間總線,注冊(cè)一個(gè)事件處理者(時(shí)間消費(fèi)者
EventBus eventBus = this.vertx.eventBus();
eventBus.consumer(GET_HELLO_MSG_SERVICE_ADDRESS).handler(msg -> {
//獲取時(shí)間內(nèi)容吼,調(diào)用service服務(wù)
System.out.println("eventBus里面的消息是" + msg.body());
List<User> users = userService.getAll();
//把結(jié)果返回消息體
msg.reply(users.toString());
});
- 注冊(cè)執(zhí)行端元
//我們需要需要把這倆執(zhí)行端元放在一個(gè)Vertx實(shí)例里面
Vertx vertx = Vertx.vertx();
//注冊(cè)服務(wù)
vertx.deployVerticle(staticServer);
vertx.deployVerticle(springVerticle);