国产麻豆精品精东影业AV网站,,,

最專業(yè)的代練平臺(tái)開發(fā)!

資訊熱點(diǎn)
HTTP服務(wù)異步轉(zhuǎn)換實(shí)踐

發(fā)布時(shí)間:2021-6-7 分類: 行業(yè)動(dòng)態(tài)

背景

我們有一個(gè)在門戶級(jí)別提供HTTP服務(wù)的應(yīng)用程序。由于業(yè)務(wù)的復(fù)雜性,用戶請(qǐng)求的處理涉及對(duì)后端遠(yuǎn)程服務(wù)的多次調(diào)用。為了實(shí)現(xiàn)簡(jiǎn)單,目前以同步方式完成,即在處理請(qǐng)求時(shí),它將占用用于邏輯操作和同步遠(yuǎn)程調(diào)用的容器線程。這種開發(fā)方法的好處是直觀且開發(fā)成本低,但它們也引入了一些穩(wěn)定性和資源浪費(fèi)問題。對(duì)于我們的HTTP服務(wù),同步的實(shí)現(xiàn)帶來了以下三個(gè)問題。

下游服務(wù)超時(shí)導(dǎo)致的服務(wù)可用性問題。請(qǐng)求超時(shí)的一部分將導(dǎo)致HTTP服務(wù)線程池已滿,導(dǎo)致其他請(qǐng)求無法獲取線程資源并失敗。

性能問題,串行執(zhí)行多次遠(yuǎn)程服務(wù)調(diào)用,導(dǎo)致服務(wù)響應(yīng)時(shí)間過長(zhǎng)。

容量問題,有限的服務(wù)吞吐量。每個(gè)請(qǐng)求都占用一個(gè)線程很長(zhǎng)時(shí)間,導(dǎo)致線程未得到充分利用。

為了解決這些問題,結(jié)合當(dāng)前使用的技術(shù)堆棧和適應(yīng)成本,我們對(duì)HTTP服務(wù)進(jìn)行了異步轉(zhuǎn)換。

溶液

回調(diào)地獄,在異步編程中已知,已經(jīng)阻止了許多學(xué)生。當(dāng)業(yè)務(wù)復(fù)雜時(shí),各種回調(diào)都嵌套在一起,使得代碼更容易出錯(cuò)并且不易理解。業(yè)界還有許多提供異步編程支持的框架。有三個(gè)想法:

光纖路徑

光纖可以被視為輕量級(jí)用戶線程,與操作系統(tǒng)的調(diào)度機(jī)制分離,并計(jì)劃在應(yīng)用程序級(jí)別進(jìn)行管理。因?yàn)樗痪S護(hù)基本的執(zhí)行堆棧信息而不立即分配執(zhí)行資源,所以它可以輕松創(chuàng)建數(shù)千個(gè)光纖(受內(nèi)存大小限制)并使用極少的線程調(diào)度光纖調(diào)度。執(zhí)行。這個(gè)方向的代表是微信團(tuán)隊(duì)的開源libco和語言級(jí)支持的Go語言。 Libco掛鉤底層IO相關(guān)系統(tǒng)功能,并通過底層IO事件驅(qū)動(dòng)光纖調(diào)度。遇到同步網(wǎng)絡(luò)調(diào)用時(shí),libco會(huì)自動(dòng)注冊(cè)回調(diào)偵聽器并放棄CPU。當(dāng)IO事件完成或隨著時(shí)間的推移,光纖會(huì)自動(dòng)恢復(fù),然后安排執(zhí)行。它的實(shí)現(xiàn)機(jī)制確定它非常適合依賴于耗時(shí)的IO服務(wù)的實(shí)現(xiàn)。它是微信數(shù)萬電話的基石。不幸的是,libco是一個(gè)高效的c/c ++協(xié)程庫(kù),它沒有在JVM上實(shí)現(xiàn)。

Quasar在JVM頂部實(shí)現(xiàn)光纖機(jī)制。基本上,異步代碼可以基于Quasar的類庫(kù)以同步模式編寫。在實(shí)際執(zhí)行代碼之前,相關(guān)字節(jié)碼以編譯或儀器代理的形式編織。從頭開始引入光纖仍然是一個(gè)不錯(cuò)的選擇。對(duì)于現(xiàn)有項(xiàng)目的轉(zhuǎn)換,有必要將現(xiàn)有的線程類修改為光纖類,這需要在底層更改大量中間件。此外,業(yè)界發(fā)表的經(jīng)驗(yàn)較少,后續(xù)可以繼續(xù)關(guān)注其發(fā)展。

演員模特

Actor模型并不是一個(gè)真正的新概念。近年來出現(xiàn)了越來越受歡迎的趨勢(shì)。 Actor模型中的核心概念是Actor實(shí)體。每個(gè)Actor實(shí)體負(fù)責(zé)邏輯計(jì)算。傳統(tǒng)的并發(fā)編程基于共享內(nèi)存的方式來實(shí)現(xiàn)多個(gè)線程之間的通信。演員不共享數(shù)據(jù),也不直接溝通。相反,他們發(fā)送或接受郵箱/隊(duì)列中的消息以實(shí)現(xiàn)通信。演員由消息驅(qū)動(dòng)。正式由于發(fā)送方和接收方的分離,Actor具有固有的并發(fā)特性,可以優(yōu)化IO等待問題,而不考慮actor之間的同步問題和接收消息的Actor的無限制調(diào)度。 Scala,Golang等在語言級(jí)別支持Actor模型。在新版本的Scala中,推出Akka以完成Actor模型并具有Java版本。但是,有必要引入一個(gè)新的API來將現(xiàn)有業(yè)務(wù)代碼塊轉(zhuǎn)換為Actor模型,該模型對(duì)現(xiàn)有代碼進(jìn)行了大量更改。

RX

Rx也是一種編程模型,它試圖提供統(tǒng)一的異步編程接口包來操作可觀察的數(shù)據(jù)流。它吸收了函數(shù)式編程的優(yōu)秀思想,實(shí)現(xiàn)了觀察者和迭代器模式的精致實(shí)現(xiàn)。目前流行的語言基本上有相應(yīng)的實(shí)現(xiàn)。例如,RxJava類庫(kù)提供了java版本的實(shí)現(xiàn),并且RxJava已成功應(yīng)用于Netulx Zuul項(xiàng)目。 Rx看起來更像是編程思維的突破。它提供了統(tǒng)一的函數(shù)式編程接口,以簡(jiǎn)化異步程序的編寫,并在內(nèi)部通過回調(diào)機(jī)制,它可以獲得比Actor更好的響應(yīng)速度。在研究過程中,我們發(fā)現(xiàn)它還需要對(duì)現(xiàn)有代碼進(jìn)行重大更改,并將之前的同步模式轉(zhuǎn)換為函數(shù)式編程風(fēng)格。

總的來說,上述一些優(yōu)秀的框架不能立即用于我們的項(xiàng)目,引入成本仍然很高。結(jié)合現(xiàn)有技術(shù)架構(gòu)并且產(chǎn)品快速迭代,我們對(duì)HTTP服務(wù)進(jìn)行了輕量級(jí)異步轉(zhuǎn)換。此轉(zhuǎn)換引入了基于圖形的執(zhí)行引擎,以解決服務(wù)之間的復(fù)雜依賴關(guān)系并集中管理異步狀態(tài)。結(jié)合Servlet 3.0,它提供了一個(gè)用于請(qǐng)求和釋放tomcat容器線程的接口,充分利用了Servlet容器線程資源。最后,spring mvc的異步模塊連接這兩個(gè)異步機(jī)制,以達(dá)到完全堆棧異步的目的。

原理分析

Servlet從3.0開始添加,并添加了異步規(guī)范。 Spring mvc還支持從3.2開始的異步Servlet 3.0。對(duì)于現(xiàn)有技術(shù)的堆棧,可以通過以下代碼說明全棧異步化:

如您所見,orderService.createOrderAsync(request)調(diào)用不會(huì)等待在發(fā)出請(qǐng)求后返回結(jié)果,而是立即返回。監(jiān)聽器在返回的未來對(duì)象上注冊(cè)。最后返回DeferredResult。 Spring mvc會(huì)在收到DeferredResult的返回結(jié)果時(shí)調(diào)用(當(dāng)然它也可以是WebAsyncTask和Callable)

AsyncContext context=HttpServletRequest.startAsync(req,response);

獲取上下文然后退出容器線程。當(dāng)createOrderAsync完成結(jié)果時(shí),將調(diào)用將來注冊(cè)的偵聽器以開始執(zhí)行。這里,忽略了一些中間處理,并且直接在DeferredResult上設(shè)置了RPC結(jié)果。通過調(diào)用Servet

的上下文調(diào)用執(zhí)行結(jié)果后的Spring mvc

Context.dispatch();

通知容器繼續(xù)后續(xù)操作,例如重新進(jìn)入spring mvc攔截器的完整進(jìn)程,最后將結(jié)果輸出到客戶端。整個(gè)過程可以用下圖表示:

圖中的三個(gè)框表示整個(gè)請(qǐng)求被分解并分三個(gè)階段執(zhí)行。第二個(gè)框的第一個(gè)框表示正在執(zhí)行RPC服務(wù)。此時(shí),處理請(qǐng)求的線程已被釋放。它可以繼續(xù)接受其他請(qǐng)求。當(dāng)RPC服務(wù)具有返回值或超時(shí)時(shí),將在單獨(dú)的線程池中引發(fā)已注冊(cè)的偵聽器。最后通知servlet容器以在第三個(gè)框中繼續(xù)interceptor.complete。通過回調(diào)通知機(jī)制,CPU將得到充分利用。避免啟動(dòng)有價(jià)值的線程以等待IO完成。

基于圖形的執(zhí)行引擎

真實(shí)的業(yè)務(wù)場(chǎng)景比上面的代碼復(fù)雜得多。例如,訂單業(yè)務(wù),一般依靠用戶,報(bào)價(jià),付款,優(yōu)惠和其他服務(wù)。服務(wù)之間存在依賴關(guān)系,例如黑名單服務(wù)驗(yàn)證傳遞提交訂單。還存在點(diǎn)對(duì)點(diǎn)的服務(wù),并且彼此之間沒有依賴關(guān)系,并且可以并行調(diào)用以減少服務(wù)的總體響應(yīng)時(shí)間。如下所示,這是一個(gè)常見的服務(wù)依賴:

在圖中,A,B和C沒有依賴關(guān)系,實(shí)際上可以并行執(zhí)行。 C服務(wù)不關(guān)心返回結(jié)果,因此呼叫通知將被發(fā)送并且可以結(jié)束。 D服務(wù)需要等待A的結(jié)果,并且E需要等待B和D的執(zhí)行結(jié)果。使用傳統(tǒng)的異步編程,這可能是它的樣子:

可以看出,服務(wù)的依賴關(guān)系隱藏在代碼行之間,業(yè)務(wù)邏輯散布在每個(gè)回調(diào)中,而ListeableFuturefutureBT在中間引入以管理異步狀態(tài)。不容易閱讀和維護(hù)。為此,我們提供了基于圖形的執(zhí)行引擎(GBEE)。 GBEE的主要目標(biāo)是解決以下問題:

(1)管理服務(wù)之間的依賴關(guān)系

服務(wù)之間的依賴關(guān)系與業(yè)務(wù)代碼分離,服務(wù)之間的依賴關(guān)系由有向非循環(huán)圖數(shù)據(jù)結(jié)構(gòu)描述。圖中的每個(gè)節(jié)點(diǎn)都保存其前一個(gè)(后驅(qū)動(dòng))節(jié)點(diǎn)。每個(gè)節(jié)點(diǎn)執(zhí)行的先決條件是其所有前任節(jié)點(diǎn)都已完成。

(2)統(tǒng)一注冊(cè)回調(diào)

每個(gè)節(jié)點(diǎn)都可以覆蓋回調(diào)以注冊(cè)自己的偵聽器。一般用于轉(zhuǎn)換結(jié)果,記錄監(jiān)控。回調(diào)由執(zhí)行者統(tǒng)一注冊(cè)。避免在代碼嵌套中注冊(cè)偵聽器。

(3)使用異步事件驅(qū)動(dòng)的執(zhí)行

異步事件偵聽器在GBEE中統(tǒng)一注冊(cè),并在事件發(fā)生時(shí)驅(qū)動(dòng)回調(diào),或者在條件成熟時(shí)調(diào)用下一個(gè)節(jié)點(diǎn)的執(zhí)行。

具體做法:

(1)將業(yè)務(wù)邏輯分成多個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)負(fù)責(zé)特定的業(yè)務(wù)邏輯執(zhí)行,但沒有任何狀態(tài),例如啟動(dòng)異步RPC調(diào)用和返回ListenableFuture。

(2)通過配置文件定義依賴關(guān)系管理

每個(gè)Node定義自己的父節(jié)點(diǎn),這意味著依賴節(jié)點(diǎn)。 Spring本身提供了服務(wù)的依賴管理功能。因此,其依賴關(guān)系定義如下:

(3)提供執(zhí)行器基于圖形的執(zhí)行器,負(fù)責(zé)統(tǒng)一注冊(cè)監(jiān)聽器和管理異步狀態(tài)。

在每個(gè)請(qǐng)求到達(dá)后,可以通過上面的依賴關(guān)系配置構(gòu)建基于圖的執(zhí)行程序:

Graph將找到根節(jié)點(diǎn),并且多個(gè)根節(jié)點(diǎn)可以同時(shí)并行。

Apply(node,context)是一個(gè)遞歸調(diào)用。每次執(zhí)行當(dāng)前節(jié)點(diǎn)時(shí),都會(huì)主動(dòng)探測(cè)父節(jié)點(diǎn)是否可以作為自己的節(jié)點(diǎn)執(zhí)行:

基于圖形的執(zhí)行程序?qū)I(yè)務(wù)代碼與底層異步機(jī)制分離,使每個(gè)節(jié)點(diǎn)更專注于自己的業(yè)務(wù)。

后記

遷移特定服務(wù)時(shí),將來會(huì)遇到一些常見問題。

(1)公司的RPC服務(wù)主要發(fā)送到dubbo,這使得使用公司的基本組件可以很容易地使用異步調(diào)用。

(2)使用tomcat 6仍然有很多應(yīng)用程序在線,tomcat 7支持Servlet 3,你應(yīng)該將應(yīng)用程序升級(jí)到tomcat 7.

(3)web.xml配置有幾個(gè)重要的配置。

為了讓spring mvc實(shí)際啟用異步支持,除了需要激活org.springframework.web.servlet.DispatcherServlet的異步選項(xiàng)外,即:true

您還需要在此servlet之前為所有過濾器設(shè)置async-supported為true。只要中間沒有設(shè)置過濾器,后者設(shè)置無效。在后續(xù)開發(fā)中,如果添加過濾器,則還必須對(duì)其進(jìn)行配置。

(4)ThreadLocal問題。

現(xiàn)有系統(tǒng)的一些常見上下文參數(shù)通過ThreadLocal傳遞。在異步轉(zhuǎn)換之后,代碼并不總是在請(qǐng)求線程中執(zhí)行。這使通過ThreadLocal傳遞的變量無效。我們采用了兩種方法來解決,一種是轉(zhuǎn)換一些業(yè)務(wù)代碼,以參數(shù)的形式傳遞。另一種是在HttpServletRequest的Attribute中存儲(chǔ)一些公共變量。在異步上下文中維護(hù)對(duì)HttpServletRequest的引用。然后通過工具類直接從HttpServletRequest中提取公共變量。

(5)異常處理

在同步代碼中,我們通常會(huì)自定義一些業(yè)務(wù)異常。捕獲這些業(yè)務(wù)異常后,我們會(huì)根據(jù)異常合理性和狀態(tài)代碼執(zhí)行一些業(yè)務(wù)邏輯。由ListeableFuture繼承的Future接口指定在異步計(jì)算期間拋出的所有異常都封裝在ExecutionException中。此時(shí),在同步代碼中捕獲,就無法捕獲ExecutionException。此時(shí),業(yè)務(wù)代碼需要修改特定類型的捕獲,然后通過Exception.getCause()獲取原始異常。這個(gè)部分可以由基于圖形的執(zhí)行引擎統(tǒng)一處理。轉(zhuǎn)換原始異常后,將調(diào)用節(jié)點(diǎn)的onException。

« 如何消除公司對(duì)搜索引擎的負(fù)面影響 | 小團(tuán)體計(jì)劃讓每個(gè)人都瘋狂地支持哪種團(tuán)體活動(dòng)可以真正引爆! »