亚洲乱码电影_久久精品视频免费播放_国产成人综合av_91精品啪在线观看国产81旧版

歡迎來到酷云建站平臺,全網(wǎng)營銷云系統(tǒng)加盟中心!

海量企業(yè)網(wǎng)站模板 · 任您選擇

美出特色,精出品質(zhì),一切為了企業(yè)更好的營銷

隱藏側(cè)欄
Beta
轉(zhuǎn)載

HTTP服務異步化改造實踐

       建站經(jīng)驗     2018-04-18     admin     55     0    

  背景

  我們在入口層有一個提供HTTP服務的應用。隨著業(yè)務的復雜,一個用戶請求的處理過程,涉及多個對后端遠程服務的調(diào)用。為了實現(xiàn)的簡單,目前都是使用同步方式完成的,也就是在一個請求的處理過程中,會占用一個容器線程進行邏輯運算和同步遠程調(diào)用。這種開發(fā)方式的好處是直觀,開發(fā)成本低,但也帶來了一些穩(wěn)定性和資源浪費的問題。對于我們的HTTP服務來說,同步化的實現(xiàn)帶來下面這3個問題。

  下游服務超時帶來的服務可用性問題。一部分的請求超時會導致HTTP服務線程池被占滿,從而導致其它的請求無法獲取到線程資源而失敗。

  性能問題,多個對遠程服務的調(diào)用串行執(zhí)行,導致服務響應時間長。

  容量問題,服務吞吐量受限。每個請求長時間占用線程,導致線程得不到充分利用。

  為了解決這些問題,結(jié)合目前使用的技術(shù)棧以及適應成本,我們對HTTP服務進行了一次異步化改造。

  


 

  解決方案

  異步化編程中聞名的Callback Hell,讓不少同學望而止步。當業(yè)務復雜的時候,各種call back互相嵌套,使代碼變得更加容易出錯和不易理解。業(yè)內(nèi)也有有不少框架提供了異步化編程支持,有以下三個思路:

  纖程

  纖程可以認為是輕量級的用戶線程,脫離了OS的調(diào)度機制,在應用級別進行調(diào)度管理。由于它只維護了基本的執(zhí)行棧信息,并不立即分配執(zhí)行資源,因此,它可以輕松創(chuàng)建成千上萬的纖程(受內(nèi)存大小的限制),通過極少的線程完成對纖程的調(diào)度執(zhí)行。這個方向的代表有微信團隊開源的libco,以及在語言層面上支持的Go語言等。libco hook了底層IO相關(guān)的系統(tǒng)函數(shù),通過底層IO事件驅(qū)動纖程的調(diào)度執(zhí)行。當遇到同步調(diào)用網(wǎng)絡(luò)請求時,libco自動注冊回調(diào)監(jiān)聽器,并讓出CPU。而在IO事件完成或者超時候,自動恢復纖程,然后調(diào)度執(zhí)行。它的實現(xiàn)機制決定了它非常適合依賴耗時IO服務的實現(xiàn)。承載了微信千萬級調(diào)用的一個基石。不過遺憾的是,libco是一個高效的c/c++協(xié)程庫,并沒有在JVM上實現(xiàn)。

  Quasar是在JVM之上實現(xiàn)了纖程機制,基本可以在Quasar的類庫基礎(chǔ)上,以同步的模式來編寫異步的代碼。在真正執(zhí)行代碼前,通過編譯或者Instrument Agent的形式織入相關(guān)的字節(jié)碼。從頭起步引入纖程還是一個不錯的選擇。對現(xiàn)有項目的改造,需要對現(xiàn)有的線程類修改成纖程類,這需要改動我們底層非常多的中間件。另外業(yè)內(nèi)公布的使用經(jīng)驗較少,后續(xù)可以持續(xù)關(guān)注它的發(fā)展。

  Actor模型

  Actor模型其實不是什么新概念了。近些年有逐漸流行的趨勢。Actor模型中一個核心概念就是Actor實體。每個Actor實體負責一個邏輯計算。傳統(tǒng)并發(fā)編程都是基于共享內(nèi)存的方式來達到多線程之間的通訊的目的。Actor之間不共享數(shù)據(jù),也不直接通訊,而是發(fā)送或者接受mailbox/queque中的消息來達到通訊的目的。Actor之間通過消息來驅(qū)動。正式由于發(fā)送者與接受者的分離,是的Actor具有內(nèi)在的并發(fā)特性,它可以不用考慮actor之間的同步問題,不受限制的調(diào)度執(zhí)行收到消息的Actor,從而優(yōu)化了IO等待的問題。Scala,Golang等在語言層面支持Actor模型。Scala的新版中,推出Akka來完成Actor模型,并有了Java版本。但是需要引入新的API,對現(xiàn)有業(yè)務代碼塊改造成Actor模型,對現(xiàn)有代碼改動較大。

  RX

  Rx也是一種編程模型,它嘗試提供統(tǒng)一的異步編程接口封裝來操作一個可觀察的數(shù)據(jù)流。其吸收了函數(shù)式編程的優(yōu)秀思想,并將觀察者,迭代器模式實現(xiàn)的淋漓精致。當下流行的語言,基本都有相應的實現(xiàn)。 如RxJava類庫,即提供了java版本的實現(xiàn),RxJava在Netflix的Zuul項目中得到成功的應用。Rx看起來更像是一種編程思想的突破。它提供了統(tǒng)一的函數(shù)式的風格編程接口來簡化異步程序的編寫,同時內(nèi)部也通過callback機制,比Actor能獲得更好的響應速度。在調(diào)研過程中,我們發(fā)現(xiàn)它同樣要求對現(xiàn)有代碼做較大改動,并將之前的同步模式轉(zhuǎn)換成函數(shù)式編程風格。

  綜合來看,以上一些優(yōu)秀的框架并不能立即利用到我們的項目中,引入成本還是很高的。結(jié)合現(xiàn)有技術(shù)架構(gòu)上,以及產(chǎn)品正在快速迭代的環(huán)境下,我們對HTTP服務進行了一次輕量級的異步化改造。這次改造,引入Graph-Based Execution Engine來解決服務之間復雜的依賴關(guān)系,集中管理異步狀態(tài)。結(jié)合Servlet 3.0提供了請求及釋放tomcat容器線程的接口,充分利用Servlet容器線程資源。最后,通過spring mvc的異步模塊銜接這兩種異步機制,達到了全棧異步化的目的。

  原理分析

  Servlet從3.0開始,增加了異步規(guī)范。spring mvc從3.2開始也支持異步Servlet 3.0。針對現(xiàn)有技術(shù)棧,實現(xiàn)全棧異步化可以通過下面的一段代碼來說明:

  


 

  可以看到,orderService.createOrderAsync(request) 這個調(diào)用在請求發(fā)出后,不等待返回結(jié)果,而是立即返回。在返回的future對象上注冊了一個監(jiān)聽器。最后返回DeferredResult。spring mvc在收到返回結(jié)果為DeferredResult(當然也可以是WebAsyncTask和Callable)時,將調(diào)用

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

  來獲取上下文,然后退出容器線程。當createOrderAsync完成得到結(jié)果后,注冊在future上的監(jiān)聽器被喚起開始執(zhí)行,此處忽略中間的一些處理,直接將RPC結(jié)果設(shè)置在DeferredResult上。spring mvc在獲得執(zhí)行結(jié)果后,通過調(diào)用Servet的上下文

  context.dispatch();

  來通知容器繼續(xù)執(zhí)行后續(xù)操作,例如重新進入spring mvc 攔截器的complete流程,最終輸出結(jié)果到客戶端。整個流程可以用下圖表示:

  


 

  圖中3個框表示整個請求被打散在3個階段執(zhí)行。第一框到第二個框之間表示RPC服務正在執(zhí)行。此時處理請求的線程已經(jīng)釋放。它可以繼續(xù)接受處理其它請求。RPC服務有返回值或者超時的時候,會在單獨的一個線程池中喚起注冊的監(jiān)聽器。最終通知Servlet容器來繼續(xù)執(zhí)行第三個框中的interceptor.complete。通過回調(diào)通知的機制,將使CPU得到充分的利用。避免了啟動一個寶貴的線程來等待IO的完成。

  Graph-Based Execution Engine

  真實的業(yè)務場景要比上面的代碼復雜的多。例如下單業(yè)務,一般都會依賴用戶,報價,支付,優(yōu)惠等服務。服務之間存在依賴關(guān)系,如黑名單服務校驗通過才能提交訂單。還有一些服務之間處于對等關(guān)系,互相之間沒有依賴,可以并行調(diào)用,以降低服務的整體響應時間。如下圖所示,這是一個常見的服務依賴關(guān)系:

  


 

  圖中A、B、C沒有依賴關(guān)系,實際上可以并行執(zhí)行。C服務不關(guān)心返回結(jié)果,因此將調(diào)用通知發(fā)出后及可結(jié)束。D服務需要等待A的結(jié)果,E需要等待B、D的執(zhí)行結(jié)果。使用傳統(tǒng)的異步編程的話,大概是這個樣子:

  


 

  可以看到服務的依賴關(guān)系隱藏在代碼行間,業(yè)務邏輯穿插在各個callback中,中間引入了ListeableFuturefutureBT 管理異步狀態(tài)。不太易于閱讀及維護。為此,我們提供了一個Graph-Based Execution Engine(GBEE)。GBEE的主要目標在于解決以下:

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

  將服務之間的依賴關(guān)系從業(yè)務代碼中分離出來,通過一個有向無環(huán)圖的數(shù)據(jù)結(jié)構(gòu)來描述服務之間的依賴關(guān)系。圖中每個節(jié)點保存了其前驅(qū)(后驅(qū))節(jié)點。每個節(jié)點可以執(zhí)行的前提條件是其所有前驅(qū)節(jié)點都完成。

  (2)統(tǒng)一注冊callback

  每個節(jié)點可以覆寫callback,用來注冊自身的監(jiān)聽器。一般用來轉(zhuǎn)換結(jié)果,記錄監(jiān)控。callback統(tǒng)一由執(zhí)行器管理注冊。避免在代碼嵌套中注冊監(jiān)聽器。

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

  在GBEE中統(tǒng)一注冊異步事件監(jiān)聽器,在事件發(fā)生時驅(qū)動執(zhí)行callback,或者在條件成熟時,喚起下一個節(jié)點的執(zhí)行。

  具體做法:

  (1)將業(yè)務邏輯分離成多個節(jié)點,每個節(jié)點負責具體的業(yè)務邏輯執(zhí)行,但沒有任何狀態(tài),例如發(fā)起異步RPC調(diào)用,并返回ListenableFuture。

  


 

  (2)通過配置文件來定義依賴管理

  每個Node定義了自己的parents,即表示依賴關(guān)系。spring本身提供了服務的依賴管理能力。因此其依賴關(guān)系定義如下:

  


 

  (3)提供了一個執(zhí)行器Graph-Based Executor 來負責統(tǒng)一注冊監(jiān)聽器以及管理異步狀態(tài)。

  每個請求到達后,通過上面的依賴配置,可以構(gòu)造出一個Graph-Based執(zhí)行器:

  


 

  Graph會找到根節(jié)點,多個根節(jié)點可以同時并行。

  


 

  apply(node, context) 是一個遞歸調(diào)用,每次執(zhí)行完當前node,主動探測下是否可以執(zhí)行父節(jié)點為自己的節(jié)點:

  


 

  Graph-Based Executor 將業(yè)務代碼與底層的異步機制解耦,使得各個節(jié)點更加關(guān)注自身業(yè)務。

  后記

  在遷移具體業(yè)務時,也遇到一些比較常見的問題,供后續(xù)的實施者參考。

  (1)公司RPC服務主要送是dubbo,利用公司的基礎(chǔ)組件,可以方便使用異步調(diào)用。

  (2)線上還有很多應用使用tomcat 6,Servlet 3 從tomcat 7開始支持,應該將相關(guān)應用升級到tomcat 7.

  (3)web.xml 配置有幾個比較重要的配置。

  為了讓spring mvc真正啟用異步支持,除了需要將org.springframework.web.servlet.DispatcherServlet的異步選項激活,即:true

  


 

  還需要將此servlet之前的所有filter的async-supported設(shè)置成true。只要中間有一個filter沒有設(shè)置,后面的設(shè)置都是無效的。并且在后續(xù)開發(fā)中,如果增加了filter,也一定要配置上。

  (4)ThreadLocal 問題。

  現(xiàn)有系統(tǒng)的一些通用的上下文參數(shù)通過ThreadLocal傳遞。異步化改造后,代碼并不是始終在請求線程中執(zhí)行。這就使得通過ThreadLocal傳遞的變量失效。我們采用了兩種方法來解決,一是一些業(yè)務代碼的改造,通過參數(shù)的形式來傳遞。另一種是將一些通用變量存入HttpServletRequest的Attribute里。異步上下文中保持了對HttpServletRequest的引用。然后通過工具類直接從HttpServletRequest提取公共變量。

  (5)異常處理

  在同步代碼中,一般我們會自定義一些業(yè)務異常,這些業(yè)務異常被捕獲后,根據(jù)異常理性及狀態(tài)碼,做一些業(yè)務邏輯。ListeableFuture繼承的Future接口規(guī)定了,在異步計算過程中拋出的所有異常封裝在ExecutionException中。此時,同步代碼中的catch,就不能捕獲ExecutionException了。此時業(yè)務代碼就需要修改捕獲的具體類型,然后通過Exception.getCause()來獲取原始異常。這塊可以通過Graph-Based Execution Engine統(tǒng)一處理。將原始異常轉(zhuǎn)換后,調(diào)用節(jié)點的onException.


--結(jié)束END--

本文鏈接: http://www.42wzzl.com/station/experience/1997.html (轉(zhuǎn)載時請注明來源鏈接)

 
本文標簽: 全部

下班PC閱讀不方便?

手機也可以隨時學習開發(fā)

微信關(guān)注公眾號“酷云”
"酷云平臺前端開發(fā)教學"
每日干貨技術(shù)分享
 

×

成為 酷云平臺 代理商!

關(guān)注

微信
關(guān)注

微信掃一掃
獲取最新優(yōu)惠信息

酷云平臺公眾號

客服

聯(lián)系
客服

很高興為您服務
尊敬的用戶,歡迎您咨詢,我們?yōu)樾掠脩魷蕚淞藘?yōu)惠好禮。 咨詢客服

聯(lián)系客服:

在線QQ: 3206174

客服電話: 0516-83703228

售前咨詢 售后服務
在線交談 智能小云

工作時間:

周一至周五: 09:00 - 17:00

WAP

手機
訪問

移動端訪問
手機上也能選模板

酷云平臺手機端