當執行的查詢數量很大時,數據存儲庫通常是高要求系統的瓶頸。延遲批處理執行器(DelayedBatchExecutor)是一個組件,可通過在Java多線程應用程序中對所需查詢進行批處理來減少所需查詢的數量。
n個查詢1個參數與1個查詢n個參數
讓我們假設一個Java應用程序執行對關系數據庫的查詢,以在給定其唯一標識符(id)的情況下檢索Product實體(行)。
查詢看起來像這樣:
現在,要檢索n種產品,可以通過兩種方法進行:
對一個參數執行n個獨立查詢:
使用IN運算符或OR的組合,對n個參數執行一次查詢以同時檢索n個產品
后者在網絡流量和數據庫服務器資源(CPU和磁盤)方面更為有效,因為:
到數據庫的往返次數為1,而不是n。
數據庫引擎針對n個參數優化了其數據遍歷過程,即,它可能只需要對每個表進行一次掃描而不是n次掃描。
這不僅適用于SELECT操作,而且適用于其他操作,例如INSERT,UPDATE和DELETE,實際上,JDBC API包括這些操作的批處理操作。
這同樣適用于NoSQL存儲庫,其中大多數都顯式提供BULK操作。
延遲批處理執行器
需要從數據庫檢索數據的Java應用程序(如REST微服務或異步消息處理器)通常實現為多線程應用程序(* 1),其中:
每個線程在其執行的某個時刻執行相同的查詢(每個查詢具有不同的參數)。
并發線程數很高(每秒數十或數百)。
在這種情況下,數據庫很可能在很短的時間間隔內多次執行相同的查詢。
如前所述,如果將這1個參數的n個查詢替換為具有n個參數的單個等效查詢,則應用程序將使用較少的數據庫服務器和網絡資源。
好消息是,可以通過以下涉及時間窗口的機制來實現它 :
第一個嘗試執行查詢的線程將打開一個時間窗口,因此其參數存儲在列表中,并且該線程已暫停。在時間窗口內執行相同查詢的其余線程會將其參數添加到列表中,并且也會被暫停。此時,尚未在數據庫上執行任何查詢。
時間窗口結束或列表已滿(預先定義了最大容量限制)后,便會使用列表中存儲的所有參數執行單個查詢。最后,一旦數據庫提供了該查詢的結果,每個線程將接收其相應的結果,并且所有線程將自動恢復。
我為自己(延遲批處理執行器)構建了此機制的簡單輕便的實現,可以輕松在新的或現有的應用程序中使用。它基于 Reactor庫,并且使用帶有通量的Flux緩沖發布者作為參數列表。
使用延遲批處理執行器的吞吐量和延遲分析
讓我們假設一個針對產品的REST微服務,它公開了一個端點,用于從給定的數據庫中檢索產品數據 productId。如果不使用 延遲批處理執行器,則說到端點每秒有200次命中,則數據庫每秒執行200個查詢。如果端點使用的 時間窗口延遲批處理執行器配置為50毫秒,最大容量 = 10個參數,則數據庫每秒僅執行20個查詢,每個參數10個參數,但代價是最多在50毫秒內增加延遲(* 2)對于每個線程執行。
換句話說,為了將等待時間增加50 ms(* 2),在保持系統整體吞吐量的同時,數據庫每秒收到的查詢減少了10倍。
其他有趣的配置:
窗口時間 = 100毫秒,最大容量 = 20個參數→20個參數的10個查詢(查詢減少20倍)
窗口時間 = 500毫秒,最大容量 = 100個參數→2個查詢,共100個參數(查詢減少100倍)
延遲批處理執行器在行動
深入研究Product微服務示例,假設對于每個傳入的HTTP請求,微服務的控制器都要求我們檢索提供其ID的Product(Java Bean),因此它將調用該方法:
public Product getProductById(Integer productId) DAO組件的ProductDAO。
讓我們看看不帶和帶的DAO的實現 延遲批處理執行器。
沒有延遲批處理執行器
使用延遲批處理執行器
首先,延遲批處理執行器必須在DAO中創建的實例,在本例中為delayedBatchExecutorProductById。它需要以下三個參數:
時間窗口(在此示例中為50毫秒)
參數列表的最大容量(在此示例中為10個參數)
將使用參數列表調用的方法(我們將在后面詳細介紹)。在此示例中,方法是retrieveProductsByIds
注意:我們將在后面看到為什么延遲批處理執行器的標識(delayedBatchExecutor ProductById)是該類的實例DelayedBatchExecutor2
其次,DAO方法public Product getProductById(Integer productId)已經過重構,可以簡單地調用實例的execute方法,僅此delayedBatchExecutor ProductById而已。所有的“魔術”都是由DelayedBatchExecutor。
之所以delayedBatchExecutor ProductById是的實例,DelayedBatchExecutor2
如果execute方法需要接收兩個參數(例如an Integer和a String)并返回的實例Product,則定義為DelayedBatchExecutor3
最后,該 retrieveProductsByIds方法必須返回a List
如果我們使用DelayedBatchExecutor3
就是這樣。
一旦運行,執行控制器邏輯的并發線程將getProductById(Integer id)在某個時候調用該方法,并且該方法將返回相應的乘積。他們不會知道他們實際上可能已經被暫停并恢復了延遲批處理執行器.
超越數據倉庫
盡管本文與數據存儲庫有關, 延遲批處理執行器 但是可以在其他上下文中使用,例如,在對REST微服務的請求中。同樣,用一個參數啟動n個GET請求要比使用n個參數啟動1個GET要昂貴得多。
延遲批處理執行器的改進
我創建 延遲批處理執行器并使用了一段時間,以有效地處理由個人項目中的并發線程啟動的多個查詢的執行。我相信它對其他人也可能有用,所以我決定將其公開。
話雖如此,仍有很大的改進空間并可以擴展所提供的功能 延遲批處理執行器。最有趣的是能夠根據執行的特定條件動態更改參數 延遲批處理執行器(窗口時間和最大容量),以最大程度地減少等待時間,同時利用具有n個參數的查詢。
-
JAVA
+關注
關注
19文章
2966瀏覽量
104702 -
線程
+關注
關注
0文章
504瀏覽量
19675
發布評論請先 登錄
相關推薦
評論