瞭解Vert.x:事件迴圈
讓Vert.x框架實現高度可擴充套件和高效能的核心是事件迴圈,更具體地說是Multi-Reactor模式,以及它的訊息匯流排,在Vert.x中稱為EventBus。
在本文中,我想解決有關事件迴圈的誤解,例如:
“Vert.x有EventLoop,所以它是單執行緒的,只使用一個CPU”?
要麼
“Vert.x是多執行緒的,所以它必須為每個Verticle建立一個執行緒”?
Multi-Reactor
Event Loop是Reactor設計模式的一個實現。
它的目標是不斷檢查新事件,並在每次新事件發生時,快速將其傳送給知道如何處理它的人。
但是通過僅使用一個執行緒來消費所有事件,我們基本上沒有充分利用我們的硬體。例如,Node.js應用程式通常會生成多個程序來解決該問題。
在提供良好隔離的同時,程序也很昂貴。Vert.x使用多個執行緒,這在系統資源方面更便宜點。
為了理解Multi-Reactor在實踐中的工作原理,我們將通過簡單的呼叫來檢查執行緒的數量 Thread.activeCount(),雖然不準確,但這足以滿足我們的目的。
讓我們先看看我們在程式開頭有多少執行緒:
Before starting VertX -> 1 thread
現在我們將啟動Vert.x應用程式:
Vertx vertx = Vertx.vertx();
再次檢查執行緒數:
After starting VertX -> 3 threads
因此,啟動Vert.x會產生2個額外的執行緒。一個是執行應用程式,另一個是呼叫vertx-blocked-thread-checker
現在讓我們部署一千個Verticle,看看它如何影響我們的執行緒數。Verticle是輕量級的actor,通常在事件迴圈上執行。
<b>final</b> Map<String, AtomicInteger> threadCounts = <b>new</b> ConcurrentHashMap<>(); <b>int</b> verticles = 1000; <b>final</b> CountDownLatch latch = <b>new</b> CountDownLatch(verticles); <b>for</b> (<b>int</b> i = 0; i < verticles; i++) { vertx.deployVerticle(<b>new</b> MyVerticle(threadCounts), c -> latch.countDown()); } latch.await();
threadCounts現在不要理會,因為它將在後面解釋。
我們在這裡使用CountDownLatch,因為Verticle是非同步部署的,我們希望確保在檢查執行緒數時已經部署了所有例項。
After deploying 1000 verticles -> 19 threads
之前我們有3個執行緒,現在又增加了16個執行緒。它們都以形式命名vert.x-eventloop-thread-X。您可以啟動一萬個Verticle,並且不會影響事件迴圈執行緒的數量。
到目前為止,有兩個重要的要點:
- Vert.x不是單執行緒的
- 事件迴圈執行緒的最大數量取決於CPU的數量,而不是部署的Verticle數量
您可以在此處檢視預設執行緒數:
ofollow,noindex" target="_blank">https://github.com/eclipse/vert.x/blob/master/src/main/java/io/vertx/core/VertxOptions.java#L38
現在是時候看看我們的Verticle是什麼樣的,為什麼我們傳遞HashMap給它:
<b>class</b> MyVerticle <b>extends</b> AbstractVerticle { <b>private</b> <b>final</b> Map<String, AtomicInteger> threadCounts; MyVerticle(Map<String, AtomicInteger> threadCounts) { <b>this</b>.threadCounts = threadCounts; } @Override <b>public</b> <b>void</b> start() { threadCounts.computeIfAbsent(Thread.currentThread().getName(), t -> <b>new</b> AtomicInteger(0)).incrementAndGet(); } }
因此,當每個Verticle啟動時,它會記錄已分配的執行緒。
此程式碼有助於我們瞭解如何在Verticle之間劃分執行緒:
vert.x-eventloop-thread-0=125
vert.x-eventloop-thread-1=125
vert.x-eventloop-thread-2=125
vert.x-eventloop-thread-3=125
vert.x-eventloop-thread-4=125
vert.x-eventloop-thread-5=125
vert.x-eventloop-thread-6=125
vert.x-eventloop-thread-7=125
如您所見,每個新Verticle以迴圈方式獲取一個執行緒。
檢視結果您可能想知道,為什麼我們部署了16個事件迴圈執行緒,但Verticle僅在前8箇中註冊。原因是我們非常積極地部署Verticle。在常規應用程式中,您可能不會這樣做。
所以,讓我們放鬆一下。我們將部署相同的千個Verticle,但這一次,一個接一個:
<b>private</b> <b>void</b> deployMyVerticle(<b>final</b> Vertx vertx, <b>final</b> Map<String, AtomicInteger> threadCounts, <b>final</b> AtomicInteger counter, <b>final</b> <b>int</b> verticles) { vertx.deployVerticle(<b>new</b> MyVerticle(threadCounts), c -> { <b>if</b> (counter.incrementAndGet() < verticles) { deployMyVerticle(vertx, threadCounts, counter, verticles); } }); }
結果是我們使用的執行緒比以前少:
vert.x-eventloop-thread-0 = 250
vert.x-eventloop-thread-1 = 250
vert.x-eventloop-thread-2 = 250
vert.x-eventloop-thread-3 = 250
那是因為框架有足夠的時間來做出反應。
Worker Verticle
worker verticle用於執行長時間執行或阻塞任務。讓我們現在以類似的方式部署一千個worker Verticle,看看會發生什麼:
<b>final</b> CountDownLatch workersLatch = <b>new</b> CountDownLatch(verticles); <b>final</b> DeploymentOptions worker = <b>new</b> DeploymentOptions().setWorker(<b>true</b>); <b>for</b> (<b>int</b> i = 0; i < verticles; i++) { vertx.deployVerticle(<b>new</b> MyVerticle(threadCounts), worker, c -> workersLatch.countDown()); } workersLatch.await();
執行緒數:
After deploying 1000 worker verticles -> 27 threads
部署一千個worker Verticle增加了另外20個執行緒。
這是因為工作者Verticle使用一個單獨的執行緒池,預設情況下大小為20。
https://github.com/eclipse/vert.x/blob/master/src/main/java/io/vertx/core/VertxOptions.java#L43
您可以通過呼叫VertxOptions的setWorkerPoolSize()on 來控制此池的大小,然後在Vert.x初始化時傳遞它們:
<b>final</b> VertxOptions options = <b>new</b> VertxOptions().setWorkerPoolSize(10); Vertx vertx = Vertx.vertx(options);
請注意,與常規Verticle不同,worker Verticle不會線上程之間均勻分佈,因為它們用於不同的目的:
vert.x-worker-thread-0=126
vert.x-worker-thread-1=39
vert.x-worker-thread-2=94
vert.x-worker-thread-3=118
vert.x-worker-thread-4=89
vert.x-worker-thread-5=114
vert.x-worker-thread-6=222
vert.x-worker-thread-7=79
vert.x-worker-thread-8=67
vert.x-worker-thread-9=50
可以類似的方式控制事件迴圈池的大小:
<b>final</b> VertxOptions options = <b>new</b> VertxOptions().setEventLoopPoolSize(4); Vertx vertx = Vertx.vertx(options);
結論
以下是幾個要點:
- Vert.x是多執行緒框架
- 它使用受控數量的執行緒
- 對於事件迴圈任務,預設情況下,執行緒池的大小是CPU計數的兩倍
- 對於worker任務,預設情況下執行緒池的大小為20
- 可以輕鬆調整兩個執行緒池的大小