當(dāng)執(zhí)行的查詢數(shù)量很大時(shí),數(shù)據(jù)存儲庫通常是高要求系統(tǒng)的瓶頸。延遲批處理執(zhí)行器(DelayedBatchExecutor)是一個(gè)組件,可通過在Java多線程應(yīng)用程序中對所需查詢進(jìn)行批處理來減少所需查詢的數(shù)量。
n個(gè)查詢1個(gè)參數(shù)與1個(gè)查詢n個(gè)參數(shù)
讓我們假設(shè)一個(gè)Java應(yīng)用程序執(zhí)行對關(guān)系數(shù)據(jù)庫的查詢,以在給定其唯一標(biāo)識符(id)的情況下檢索Product實(shí)體(行)。
查詢看起來像這樣:
現(xiàn)在,要檢索n種產(chǎn)品,可以通過兩種方法進(jìn)行:
對一個(gè)參數(shù)執(zhí)行n個(gè)獨(dú)立查詢:
使用IN運(yùn)算符或OR的組合,對n個(gè)參數(shù)執(zhí)行一次查詢以同時(shí)檢索n個(gè)產(chǎn)品
后者在網(wǎng)絡(luò)流量和數(shù)據(jù)庫服務(wù)器資源(CPU和磁盤)方面更為有效,因?yàn)椋?/p>
到數(shù)據(jù)庫的往返次數(shù)為1,而不是n。
數(shù)據(jù)庫引擎針對n個(gè)參數(shù)優(yōu)化了其數(shù)據(jù)遍歷過程,即,它可能只需要對每個(gè)表進(jìn)行一次掃描而不是n次掃描。
這不僅適用于SELECT操作,而且適用于其他操作,例如INSERT,UPDATE和DELETE,實(shí)際上,JDBC API包括這些操作的批處理操作。
這同樣適用于NoSQL存儲庫,其中大多數(shù)都顯式提供BULK操作。
延遲批處理執(zhí)行器
需要從數(shù)據(jù)庫檢索數(shù)據(jù)的Java應(yīng)用程序(如REST微服務(wù)或異步消息處理器)通常實(shí)現(xiàn)為多線程應(yīng)用程序(* 1),其中:
每個(gè)線程在其執(zhí)行的某個(gè)時(shí)刻執(zhí)行相同的查詢(每個(gè)查詢具有不同的參數(shù))。
并發(fā)線程數(shù)很高(每秒數(shù)十或數(shù)百)。
在這種情況下,數(shù)據(jù)庫很可能在很短的時(shí)間間隔內(nèi)多次執(zhí)行相同的查詢。
如前所述,如果將這1個(gè)參數(shù)的n個(gè)查詢替換為具有n個(gè)參數(shù)的單個(gè)等效查詢,則應(yīng)用程序?qū)⑹褂幂^少的數(shù)據(jù)庫服務(wù)器和網(wǎng)絡(luò)資源。
好消息是,可以通過以下涉及時(shí)間窗口的機(jī)制來實(shí)現(xiàn)它 :
第一個(gè)嘗試執(zhí)行查詢的線程將打開一個(gè)時(shí)間窗口,因此其參數(shù)存儲在列表中,并且該線程已暫停。在時(shí)間窗口內(nèi)執(zhí)行相同查詢的其余線程會將其參數(shù)添加到列表中,并且也會被暫停。此時(shí),尚未在數(shù)據(jù)庫上執(zhí)行任何查詢。
時(shí)間窗口結(jié)束或列表已滿(預(yù)先定義了最大容量限制)后,便會使用列表中存儲的所有參數(shù)執(zhí)行單個(gè)查詢。最后,一旦數(shù)據(jù)庫提供了該查詢的結(jié)果,每個(gè)線程將接收其相應(yīng)的結(jié)果,并且所有線程將自動(dòng)恢復(fù)。
我為自己(延遲批處理執(zhí)行器)構(gòu)建了此機(jī)制的簡單輕便的實(shí)現(xiàn),可以輕松在新的或現(xiàn)有的應(yīng)用程序中使用。它基于 Reactor庫,并且使用帶有通量的Flux緩沖發(fā)布者作為參數(shù)列表。
使用延遲批處理執(zhí)行器的吞吐量和延遲分析
讓我們假設(shè)一個(gè)針對產(chǎn)品的REST微服務(wù),它公開了一個(gè)端點(diǎn),用于從給定的數(shù)據(jù)庫中檢索產(chǎn)品數(shù)據(jù) productId。如果不使用 延遲批處理執(zhí)行器,則說到端點(diǎn)每秒有200次命中,則數(shù)據(jù)庫每秒執(zhí)行200個(gè)查詢。如果端點(diǎn)使用的 時(shí)間窗口延遲批處理執(zhí)行器配置為50毫秒,最大容量 = 10個(gè)參數(shù),則數(shù)據(jù)庫每秒僅執(zhí)行20個(gè)查詢,每個(gè)參數(shù)10個(gè)參數(shù),但代價(jià)是最多在50毫秒內(nèi)增加延遲(* 2)對于每個(gè)線程執(zhí)行。
換句話說,為了將等待時(shí)間增加50 ms(* 2),在保持系統(tǒng)整體吞吐量的同時(shí),數(shù)據(jù)庫每秒收到的查詢減少了10倍。
其他有趣的配置:
窗口時(shí)間 = 100毫秒,最大容量 = 20個(gè)參數(shù)→20個(gè)參數(shù)的10個(gè)查詢(查詢減少20倍)
窗口時(shí)間 = 500毫秒,最大容量 = 100個(gè)參數(shù)→2個(gè)查詢,共100個(gè)參數(shù)(查詢減少100倍)
延遲批處理執(zhí)行器在行動(dòng)
深入研究Product微服務(wù)示例,假設(shè)對于每個(gè)傳入的HTTP請求,微服務(wù)的控制器都要求我們檢索提供其ID的Product(Java Bean),因此它將調(diào)用該方法:
public Product getProductById(Integer productId) DAO組件的ProductDAO。
讓我們看看不帶和帶的DAO的實(shí)現(xiàn) 延遲批處理執(zhí)行器。
沒有延遲批處理執(zhí)行器
使用延遲批處理執(zhí)行器
首先,延遲批處理執(zhí)行器必須在DAO中創(chuàng)建的實(shí)例,在本例中為delayedBatchExecutorProductById。它需要以下三個(gè)參數(shù):
時(shí)間窗口(在此示例中為50毫秒)
參數(shù)列表的最大容量(在此示例中為10個(gè)參數(shù))
將使用參數(shù)列表調(diào)用的方法(我們將在后面詳細(xì)介紹)。在此示例中,方法是retrieveProductsByIds
注意:我們將在后面看到為什么延遲批處理執(zhí)行器的標(biāo)識(delayedBatchExecutor ProductById)是該類的實(shí)例DelayedBatchExecutor2
其次,DAO方法public Product getProductById(Integer productId)已經(jīng)過重構(gòu),可以簡單地調(diào)用實(shí)例的execute方法,僅此delayedBatchExecutor ProductById而已。所有的“魔術(shù)”都是由DelayedBatchExecutor。
之所以delayedBatchExecutor ProductById是的實(shí)例,DelayedBatchExecutor2
如果execute方法需要接收兩個(gè)參數(shù)(例如an Integer和a String)并返回的實(shí)例Product,則定義為DelayedBatchExecutor3
最后,該 retrieveProductsByIds方法必須返回a List
如果我們使用DelayedBatchExecutor3
就是這樣。
一旦運(yùn)行,執(zhí)行控制器邏輯的并發(fā)線程將getProductById(Integer id)在某個(gè)時(shí)候調(diào)用該方法,并且該方法將返回相應(yīng)的乘積。他們不會知道他們實(shí)際上可能已經(jīng)被暫停并恢復(fù)了延遲批處理執(zhí)行器.
超越數(shù)據(jù)倉庫
盡管本文與數(shù)據(jù)存儲庫有關(guān), 延遲批處理執(zhí)行器 但是可以在其他上下文中使用,例如,在對REST微服務(wù)的請求中。同樣,用一個(gè)參數(shù)啟動(dòng)n個(gè)GET請求要比使用n個(gè)參數(shù)啟動(dòng)1個(gè)GET要昂貴得多。
延遲批處理執(zhí)行器的改進(jìn)
我創(chuàng)建 延遲批處理執(zhí)行器并使用了一段時(shí)間,以有效地處理由個(gè)人項(xiàng)目中的并發(fā)線程啟動(dòng)的多個(gè)查詢的執(zhí)行。我相信它對其他人也可能有用,所以我決定將其公開。
話雖如此,仍有很大的改進(jìn)空間并可以擴(kuò)展所提供的功能 延遲批處理執(zhí)行器。最有趣的是能夠根據(jù)執(zhí)行的特定條件動(dòng)態(tài)更改參數(shù) 延遲批處理執(zhí)行器(窗口時(shí)間和最大容量),以最大程度地減少等待時(shí)間,同時(shí)利用具有n個(gè)參數(shù)的查詢。
-
JAVA
+關(guān)注
關(guān)注
20文章
3001瀏覽量
116422 -
線程
+關(guān)注
關(guān)注
0文章
509瀏覽量
20826
發(fā)布評論請先 登錄
【瑞薩RA × Zephyr評測】多線程和看門狗
解析Linux的進(jìn)程、線程和協(xié)程
多線程的系統(tǒng)
Linux多線程對比單線程的優(yōu)勢
PYQT 應(yīng)用程序框架及開發(fā)工具
rt-thread studio 如何進(jìn)行多線程編譯?
【HZ-T536開發(fā)板免費(fèi)體驗(yàn)】—— linux創(chuàng)建線程
從底層解讀labview的TDMS高級異步寫入的工作原理
UVC+MSC實(shí)現(xiàn)中MSC線程未運(yùn)行的原因?
多線程的安全注意事項(xiàng)
什么是ArkTS?
使用Percepio View免費(fèi)跟蹤工具分析Zephyr應(yīng)用
Java開發(fā)者必備的效率工具——Perforce JRebel是什么?為什么很多Java開發(fā)者在用?
Java多線程應(yīng)用程序的數(shù)據(jù)存儲庫使用改進(jìn)
評論