vertx初探

背景

vertx是一個我去年看完netty就一直想看看的工具,但由於拖延加懶,最近纔看了看文檔寫了寫demo, 算是對它有了一點點了解,之因此想寫一點,實際上是想本身給本身總結下vertx的一些核心的概念。java

vertx core

vertx core 提供了一些 vertx的基本操做,如常常用到的node

  1. 編寫TCP客戶端和服務器
  2. 編寫HTTP客戶端和服務器
  3. EventBus
  4. file操做
  5. HA
  6. 集羣

先上一段代碼看下vertx建立一個httpServer:python

//建立一個vertx實例
        VertxOptions vo = new VertxOptions();
        vo.setEventLoopPoolSize( 1);
        Vertx vertx = Vertx.vertx(vo);

        vertx.deployVerticle(MyFirstVerticle.class.getName()); 
        DeploymentOptions().setInstances(2)

        //MyFirstVerticle.java
        public class MyFirstVerticle extends AbstractVerticle {
        public void start() {

        vertx.createHttpServer().requestHandler(req -> {
            System.out.println(Thread.currentThread());
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            req.response()
                    .putHeader("content-type", "text/plain")
                    .end("Hello World!");
            this.deploymentID();
            Context c=vertx.getOrCreateContext();
        }).listen(8080);

    }
}

vertx實例是最核心的一個對象 是寧作幾乎一切事情的基礎,包括建立客戶端和服務器、獲取事件總線的引用、設置定時器等等。
是否是很想說一句,嗨,就這,不就nodejs嗎mysql

EventLoop

EventLoop算是vertx的基本模型了,簡單的講就是全部的操做都是以 觸發 的方式來進行,將IO的操做徹底交給vertx,開發者真正要關心的是IO各個階段的 事件 ,講的樸素一點就像是js的回調函數同樣。
我的以爲vertx的EventLoop 基本等同於Netty的模型,若是真要探索起來,怕是要從Linux的多路複用,select函數,java的NIO,和netty一路將過來了,因此嘗試用畫圖的方式來更形象的描繪:
eventLoop.pnggit

其實EventLoop 就是一條線程,上面掛的每個channel就是一個IO鏈接,底層利用的就是IO多路複用加select 或poll,epoll,來保證每個線程能夠保證控制不少個IO鏈接,而每個鏈接都會掛一個handler,來處理這個鏈接的 每個事件 例如:init,connected,read.writer,close。
這個模型的左半部分一樣適用於netty。右半部分有一個workPool是一個線程池,是vertx新增的東西,是用來專門處理耗時操做的,如 file,阻塞的DB操做,爲何要加這個概念哪,由於不要阻塞EventLoop是NIO的基本守則。
阻塞操做操做代碼以下:github

executor.executeBlocking(future -> {
            System.out.println("Action Thread"+Thread.currentThread());
            // 調用一些須要耗費顯著執行時間返回結果的阻塞式API
            String result = blockingMethod("hello");
            future.complete(result);
        }, res -> {
            System.out.println("Handler Thread"+Thread.currentThread());
            System.out.println("The result is: " + res.result());
            executor.close();
        });

verticle

verticle 其實一直是讓我困惑的一個概念,由於vertx的主要運行,基本都是圍繞vertx實例來進行的,後面我爲verticle找到了一個合理的角色,叫他爲vertx實例的一個發佈單元,什麼是一個 發佈單元哪,舉個例子,一個HttpServer的發佈是一個發佈單元。
verticle具備如下幾個特色:web

  1. 每一個verticle佔用一個EventLoop線程,且只對應一個EventLoop
  2. 每一個verticle中建立的HttpServer,EventBus等等,都會在這個verticle回收時同步回收
  3. 在多個verticle中建立一樣端口的httpServer,不會有錯誤,會變成兩個EventLoop線程處理同一個HttpServer的鏈接,因此多核機器,確定是須要設置多個verticle的實例來增強性能的。

能夠這麼想,vertx實例就是一臺服務器,而verticle就是上面跑的進程。spring

EventBus

EventBus 是溝通verticle的橋樑,且可溝通集羣中不一樣vertx實例的verticle,操做很簡單。這個彷佛概念很簡單,就是隊列唄,上段代碼看看:sql

//建立一個EventBus
            EventBus eb = vertx.eventBus();

            req.bodyHandler(totalBuffer -> {
                eb.send("news.uk.sport", totalBuffer.toString("UTF-8"));
            });

            //消費
            EventBus eb = vertx.eventBus();
            eb.consumer("news.uk.sport", message -> {
            System.out.println("前臺傳入的內容:" + message.body()+""+this);
            });

模型如圖:
eventBus.png數據庫

vertx web

vertx core已經提供了基本的HttpServer的操做,但實際上功能遠遠不夠正常的開發,因此vertx web做爲一個拓展包,是web開發必須的。他提供了一個基本概念router,來進行各類匹配,使得請求能進入正確的handler,其實就是有點springMvc的各類路徑匹配。使用起來代碼:

HttpServer server = vertx.createHttpServer();
 

        Router router = Router.router(vertx);

        router.route("/some/path/").handler(routingContext -> {

            HttpServerResponse response = routingContext.response();
            // 若是不能一次輸出全部的response,就須要設置爲true
            response.setChunked(true);

            response.write("route1\n");
 
        });
        server.requestHandler(router).listen(8080);

web包括的功能不少,有各類匹配,content-type匹配,路徑規則匹配,CROS,錯誤處理,文件傳輸 等等

vertx fileSystem

vretx中的文件操做主要採用了javaNio包中的文件操做,經過看源碼咱們能夠發現文件操做也是運行在workPool中的,看下代碼:

fs.copy("C:\\Users\\Administrator\\Desktop\\Untitled-4.txt", "C:\\Users\\Administrator\\Desktop\\ss.txt", res -> {
                System.out.println("file copy handle" + System.currentTimeMillis());
                System.out.println("file copy handle" + Thread.currentThread());
                if (res.succeeded()) {
                    System.out.println("success");
                } else {
                    System.out.println("error");
                }
            })

源碼:

//FileSystemImpl.java

    /**
     * Run the blocking action using a thread from the worker pool.
     */
    public void run() {
      context.executeBlockingInternal(this, handler);
    }

vertx DB

vertx其實提供了 兩種數據庫操做。一種是正常的jdbc操做,一種是異步非阻塞的數據庫操做,可是隻限於PG和mysql。

jdbc操做

看一段代碼:

JDBCClient client = JDBCClient.createShared(vertx, new JsonObject()
                .put("url", "jdbc:postgresql://localhost:5432/postgres")
                .put("driver_class", "org.postgresql.Driver")
                .put("max_pool_size", 30).put("user","postgres").put("password","postgres"));

client.getConnection(res -> {
                if (res.succeeded()) {
                    SQLConnection connection = res.result();
                    
                    connection.query("SELECT * FROM userb", res2 -> {
                        if (res2.succeeded()) {
                            ResultSet rs = res2.result();
                            System.out.println(rs.toJson());
                        }
                    });
                } else {
                    System.out.println("鏈接失敗");                }
            });

點進去發現:其實作的仍是阻塞操做,

//AbstractJDBCAction.java
    public void execute(Connection conn, TaskQueue statementsQueue, Handler<AsyncResult<T>> resultHandler) {
        this.ctx.executeBlocking((future) -> {
            this.handle(conn, future);
        }, statementsQueue, resultHandler);
    }

異步數據庫操做

這我一直很好奇的,由於jdbc在自然上就是阻塞的,因此要想作到數據庫的異步,就得放棄廠商提供的driver,本身作一套編解碼,看下代碼:

PgConnectOptions connectOptions = new PgConnectOptions()
                .setPort(5432)
                .setHost("localhost")
                .setDatabase("postgres")
                .setUser("postgres")
                .setPassword("postgres");

        // Pool options
        PoolOptions poolOptions = new PoolOptions()
                .setMaxSize(5);

        // Create the client pool
        PgPool client = PgPool.pool(vertx, connectOptions, poolOptions);
    
    client.query("SELECT * FROM  userb ", ar -> {
                if (ar.succeeded()) {
                    RowSet<Row> result = ar.result();
                    result.forEach((r) -> response.write((String)r.getValue("name")));
                    response.end();

                } else {
                    System.out.println("Failure: " + ar.cause().getMessage();
                }
            });

這樣就能作到數據庫的異步操做,且能夠包含事務的控制。簡直絲滑

關於數據庫異步的思考

數據庫異步看起來十分黑科技,可是咱們應該明白的是,異步非阻塞的NIO最大的優勢就在於利用不多的線程來控制更多的IO鏈接,這使得在超多鏈接,IO密集的操做中有更好的表現,可是數據庫鏈接原本一個java系統就不會佔用幾個,記得之前看過一個文章講鏈接數設置最好=CPUcore2磁盤數,那麼數據庫的NIO在通常的業務系統中,彷佛並沒必要要。
插一個知乎上大佬的回答:

https://www.zhihu.com/questio...

vertx模型圖

模型.png
簡單明瞭

vertx的缺點

說到缺點,明顯就是回調地獄了,主要仍是看過 python的協程,因此不免拿來作個比較,

摘抄自廖雪峯的python教程
import asyncio

from aiohttp import web

async def index(request):
    await asyncio.sleep(0.5)
    return web.Response(body=b'<h1>Index</h1>')

async def hello(request):
    await asyncio.sleep(0.5)
    text = '<h1>hello, %s!</h1>' % request.match_info['name']
    return web.Response(body=text.encode('utf-8'))

async def init(loop):
    app = web.Application(loop=loop)
    app.router.add_route('GET', '/', index)
    app.router.add_route('GET', '/hello/{name}', hello)
    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
    print('Server started at http://127.0.0.1:8000...')
    return srv

loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()

能夠看到協程 能夠將本該作回調的response handler變成了更符合編程習慣的方式,仍是比較舒服的。

講點廢話

原本還想把demo放到github上來給像我同樣懶的人直接clone下來跑的,可是確實都是官網的例子,放上去太羞恥了。 這文章寫的很糾結,寫詳細了東西太多,寫總結性的東西吧,太抽象,沒看過的以爲一臉懵,懂得以爲寫的沒啥意義。 就這吧,若是有大佬想和我討論或是哪裏寫的不對,請積極發炎。

相關文章
相關標籤/搜索