瞭解Vert.x:事件迴圈

banq發表於2018-12-06

讓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,通常在事件迴圈上執行。

final Map<String, AtomicInteger> threadCounts = new ConcurrentHashMap<>();

int verticles = 1000;
final CountDownLatch latch = new CountDownLatch(verticles);
for (int i = 0; i < verticles; i++) {
    vertx.deployVerticle(new 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數量


您可以在此處檢視預設執行緒數:

L38>https://github.com/eclipse/vert.x/blob/master/src/main/java/io/vertx/core/VertxOptions.javaL38

現在是時候看看我們的Verticle是什麼樣的,為什麼我們傳遞HashMap給它:

class MyVerticle extends AbstractVerticle {
    private final Map<String, AtomicInteger> threadCounts;

    MyVerticle(Map<String, AtomicInteger> threadCounts) {
        this.threadCounts = threadCounts;
    }

    @Override
    public void start() {
        threadCounts.computeIfAbsent(Thread.currentThread().getName(),
                t -> new 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,但這一次,一個接一個:

private void deployMyVerticle(final Vertx vertx,
                              final Map<String, AtomicInteger> threadCounts,
                              final AtomicInteger counter,
                              final int verticles) {
    vertx.deployVerticle(new MyVerticle(threadCounts), c -> {
        if (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,看看會發生什麼:

final CountDownLatch workersLatch = new CountDownLatch(verticles);
final DeploymentOptions worker = new DeploymentOptions().setWorker(true);
for (int i = 0; i < verticles; i++) {
    vertx.deployVerticle(new MyVerticle(threadCounts), worker, c -> workersLatch.countDown());
}
workersLatch.await();


執行緒數:
After deploying 1000 worker verticles -> 27 threads

部署一千個worker  Verticle增加了另外20個執行緒。
這是因為工作者Verticle使用一個單獨的執行緒池,預設情況下大小為20。

L43>https://github.com/eclipse/vert.x/blob/master/src/main/java/io/vertx/core/VertxOptions.javaL43

您可以透過呼叫VertxOptions的setWorkerPoolSize()on 來控制此池的大小,然後在Vert.x初始化時傳遞它們:

final VertxOptions options = new 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

可以類似的方式控制事件迴圈池的大小:

final VertxOptions options = new VertxOptions().setEventLoopPoolSize(4);
Vertx vertx = Vertx.vertx(options);


結論
以下是幾個要點:

  • Vert.x是多執行緒框架
  • 它使用受控數量的執行緒
  • 對於事件迴圈任務,預設情況下,執行緒池的大小是CPU計數的兩倍
  • 對於worker任務,預設情況下執行緒池的大小為20
  • 可以輕鬆調整兩個執行緒池的大小

相關文章