乾貨 | Reactive模式在Trip.com消息推送平臺上的實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、背景"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.1 業務需求"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Trip.com消息推送平臺主要負責Trip.com在海外的郵件等渠道的營銷消息推送,系統整體設計爲面向上游消息的流式架構,當接收到上游的請求之後,經過一系列的計算邏輯,最後將會調用下游第三方發送接口,將郵件等消息通過網絡發送出去。Trip.com消息推送平臺是典型的IO密集型應用。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.2 當前解決方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Trip.com的業務層以Java技術棧爲主,其中主要web服務基於同步+阻塞IO的servlet模型,也有少部分web服務基於異步servlet。servlet是經典的JavaEE解決方案,旨在用多線程模型解決IO高併發問題。這種同步編程模型的優點是開發簡單,易於進行問題追蹤,並且對開發人員的要求比較低,有利於業務的快速開發。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/c1\/c13da6363eb544f582ac4236c48bf323.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tirp.com消息推送平臺也是基於同步+阻塞IO的servlet模型架構。當客戶端發起網絡請求的時候,請求首先會由Tomcat容器的Acceptor線程進行處理,將Channel放到待處理請求隊列然後通過Poller線程進行IO多路複用的監聽,當Poller監聽到Channel的可讀事件後,請求體將會從緩衝區被讀入內存,然後交由Tomcat容器的Worker線程池進行消費。由於需要使用阻塞IO調用下游的第三方發送接口,所以Worker線程池需要啓動大量的線程進行併發操作,根據Tomcat配置文件,最多可能啓動1024個worker線程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"代碼示例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Trip.com消息推送平臺使用AWS的SES服務進行郵件發送,在發送Email時將會調用AWS的同步SDK:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nSendEmailResult sendEmail(SendEmailRequest sendEmailRequest);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而AWS的同步SDK使用的是apache的HttpClient,底層採用的BIO模式將會阻塞當前線程,直到會話緩衝區有數據到達或到達超時時間。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、存在的問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而隨着業務量上漲帶來上游消息負載增加,原有的阻塞IO模型在高併發下,會有大量線程處於阻塞狀態,導致應用需要囤積大量的線程以應對峯值壓力。過多的線程將會造成大量的內存佔用和頻繁線程上下文切換的開銷,所以原有的servlet線程模型具有CPU利用率低、內存佔用大、對異常請求不具備彈性等缺點。該平臺在壓力峯值時需要部署大量機器,它主要具有以下性能上的問題:"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1 線程上下文切換開銷"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一次請求的IO時間平均在1200ms,最高能達到50000ms,而計算時間只有1~2ms,根據最佳線程公式理論上1C需要600~2500個線程。囤積如此多的線程將會造成大量的上下文切換開銷和上GB的內存佔用。但若是使用少量的線程,將可能由於線程數量的限制,導致請求量過高時拿不到處理線程,最終請求超時,不具備低延遲等特性。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2 部署成本高"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"若是採用主流的2C4G容器配置,理論上將需要1000+的線程用於處理請求,這將佔用大概1GB的內存空間。同時若併發請求數>線程數時,需要採用水平擴容增加服務的吞吐量,所以服務需要按照峯值併發進行預估部署,造成空閒時間大量資源的浪費。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.3 超時風險"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一次IO最高能達到50s,當有異常請求導致響應時間突增時,因爲會阻塞線程,導致線程池中的線程大部分都被阻塞,從而無法響應新的請求。在這種情況下,少量的異常請求將會導致上游大量的超時報錯,因此服務不具有彈性。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、解決方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"面對傳統BIO模式在IO密集型場景下的缺點,藉助NIO和多路複用技術可解決上述BIO問題,目前Java項目對接NIO的方式主要依靠回調,代碼複雜度高,降低了代碼可讀性與可維護性。隨着Reactive反應式架構的流行,業界有一些公司開始推動服務的全異步升級,開始採用Reactive架構來解決此類問題。而Trip.com也開始逐漸重視服務網絡IO的性能問題,已有部分團隊開始進行Reactive實踐。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Reactive 構建的程序代表的是異步非阻塞、函數式編程、事件驅動的思想。隨着近年來Reactive編程模式的發展,能達到高性能與可讀性的兼顧。Trip.com消息推送平臺利用Reactive相關技術對系統進行異步非阻塞IO改造,主要希望達到以下兩個目標:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)提升單機的吞吐量,提高有效CPU使用率、降低內存佔用、保證業務請求突增時系統的可伸縮性,最終降低硬件成本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)使用Reactive編程模型,替代處理NIO常用的異步回調模式,積累對同步阻塞應用進行異步非阻塞化升級的重構經驗。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1 什麼是Reactive?"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反應式宣言:來自不同領域的組織正在不約而同地發現一些看起來如出一轍的軟件構建模式。它們的系統更加穩健,更加有可回覆性,更加靈活,並且以更好的定位來滿足現代的需求。這些變化之所以會發生,是因爲近幾年的應用需求出現了戲劇性的變化。僅僅在幾年之前,大型應用意味着數十臺服務器,數秒的響應時間,數小時的離線維護時間以及若干GB的數據。而在今天,應用被部署在一切場合,從移動設備到基於雲的集羣,這些集羣運行在數以千計的多核心處理器的之上。用戶期望毫秒級的響應時間以及100%的正常運行時間。數據則以PB爲單位來衡量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"昨天的軟件架構已經完全無法地滿足今天的需求。我們相信,一種條理分明的系統架構方法是必要的,而且我們相信關於這種方法的所有必要方面已經逐一地被人們認識到:我們需要的系統是反應式的,具有可回覆性的,可伸縮的,以及以消息驅動的。我們將這樣的系統稱之爲反應式系統。以反應式系統方式構建的系統更加靈活,松耦合和可擴展。這使得它們更容易被開發,而且經得起變化的考驗。它們對於系統失敗表現出顯著的包容性,並且當失敗真的發生時,它們能用優雅的方式去應對,而不是放任災難的發生。反應式系統是高度靈敏的,能夠給用戶以有效的交互式的反饋。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Reactive宣言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/3b\/3bb5c3738b6e57fdeb15e249c0c51da0.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2013年6月,Roland Kuhn等人發佈了《反應式宣言》, 該宣言定義了反應式系統應該具備的一些架構設計原則。符合反應式設計原則的系統稱爲反應式系統。根據反應式宣言,反應式系統需要具備即時響應性(Responsive)、回彈性(Resilient)、彈性(Elastic)和消息驅動(Message Driven)四個特質。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"VALUE-即時響應性 (Responsive)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只要有可能,系統就會及時地做出響應。即時響應是可用性和實用性的基石,而更加重要的是,即時響應意味着可以快速地檢測到問題並且有效地對其進行處理。即時響應的系統專注於提供快速而一致的響應時間,確立可靠的反饋上限,以提供一致的服務質量。這種一致的行爲轉而將簡化錯誤處理、建立最終用戶的信任並促使用戶與系統做進一步的互動。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反應式系統具備及時響應性,可以提供快速的響應時間,在錯誤發生時也會保持響應性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"FORM-回彈性(Resilient)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"系統在出現失敗時依然能保持即時響應性,每個組件的恢復都被委託給了另一個外部的組件,此外,在必要時可以通過複製來保證高可用性。因此組件的客戶端不再承擔組件失敗的處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反應式系統通過背壓等特性避免錯誤在系統中的傳播,所以在失敗發生的時候,反應式系統將會對錯誤具有更強的承受處理能力。背壓是reactive stream定義的規範,可以使用rsocket、grpc這類網絡協議實現背壓的機制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"FORM-彈性(Elastic)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"系統在不斷變化的工作負載之下依然保持即時響應性。反應式系統可以對輸入負載的速率變化做出反應,比如通過橫向地伸縮底層計算資源。這意味着設計上不能有中央瓶頸,使得各個組件可以進行分片或者複製,並在它們之間進行負載均衡。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反應式系統的瓶頸不在於線程模型,在不同的工作負載下,使用EventLoop線程模型將始終提供CPU資源允許的計算能力,當達到計算能力瓶頸時可以橫向拓展CPU計算資源。反應式系統通過EventLoop+NIO模型,避免線程的上下文開銷,同時也避免線程池資源的大小成爲系統的瓶頸。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/da\/da0d0a25f531097548c932ee20b64727.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2 使用Reactive技術進行重構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.1章節我們談論了Reactive理論模型,以及它的部分技術原理。現在,我們要使用Reactive技術重構Trip.com消息發送平臺。根據reactive思想的指導,對於IO密集型應用,我們可以採用EventLoop+NIO的方式對傳統的同步阻塞IO模型進行優化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在整個系統中,首先介紹三個主要的中間件:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)Tomcat:網絡中間件,負責接收和響應網絡請求"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)RPC Framework(soa):Trip.com集團的RPC框架,提供了同步和異步兩種服務模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)AWS SDK:使用AWS的異步SDK,通過NIO調用AWS服務"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.1 線程模型設計"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在原同步版本中,首先使用Tomcat的Worker線程接收和處理request並執行同步邏輯,而後通過AWS的同步SDK進行BIO調用,此時worker線程將會block在IO調用上。當網絡IO響應時,該worker線程將被喚醒,拿到response並執行響應邏輯。同步阻塞的線程模型是比較簡單的,worker線程基本負責了整個流程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而採用EventLoop+NIO的異步非阻塞模式,將會無可避免的引入回調函數,爲了回調流程的邏輯清晰和故障隔離等功能考慮,將會引入幾組不同的回調線程池,來負責不同模塊的回調邏輯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個異步流程的線程模型設計如圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/29\/29c55716e0e286795d21a22d1403f2fa.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"request流程:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)使用Tomcat接收和處理網絡請求"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Tomcat的Acceptor線程接收socket連接事件,然後封裝成NioChannel壓入events queue中。然後Poller線程通過Selector獲取到可讀的socket,並傳遞給Worker線程進行處理。該部分與原版本的同步模型相同,Tomcat線程模型如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ef\/eff15dabc4370849f28cb7ca7a5fa95c.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)業務邏輯處理部分"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tomcat的Worker線程將負責執行同步邏輯,worker線程將會依次同步執行Tomcat邏輯、RPC Framework邏輯、業務邏輯、AWS SDK邏輯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Worker線程執行完同步邏輯之後,將會把封裝好的request放入EventLoop的events queue中,等待EventLoop的處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)AWS SDK NIO異步處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AWS的異步SDK,使用Netty進行網絡IO的傳輸,其內部會內置一個Netty的EventLoop線程池,負責網絡IO的序列化反序列化。AWS的EventLoop線程池定義如下,使用的是Netty的NioEventLoopGroup:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nint numThreads = Optional.ofNullable(builder.numberOfThreads).orElse(0);\nThreadFactory threadFactory = Optional.ofNullable(builder.threadFactory)\n .orElse(new ThreadFactoryBuilder()\n .threadNamePrefix(\"aws-java-sdk-NettyEventLoop\")\n .build());\nreturn new NioEventLoopGroup(numThreads, threadFactory);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)註冊回調函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"① channelFuture註冊回調"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Netty使用NIO進行網絡傳輸,並將對應回調函數註冊到對應的channelFuture上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"② AWS SDK註冊回調"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將channelFuture對應的Promise轉換成CompletableFuture,AWS SDK通過CompletableFuture.whenCompleteAsync方法將回調函數提交給futureCompletionExecutor線程池。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nresponseHandlerFuture.whenCompleteAsync((r, t) -> {\n if (t == null) {\n responseFuture.complete(r);\n } else {\n responseFuture.completeExceptionally(t);\n }\n}, futureCompletionExecutor);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"futureCompletionExecutor線程池的設置如下,爲上圖中的AWS SDK內置的回調線程池。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nint processors = Runtime.getRuntime().availableProcessors();\nint corePoolSize = Math.max(8, processors);\nint maxPoolSize = Math.max(64, processors * 2);\nThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,\n 10, TimeUnit.SECONDS,\n new LinkedBlockingQueue<>(1_000),\n new ThreadFactoryBuilder()\n .threadNamePrefix(\"sdk-async-response\")\n .build());"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③ 業務邏輯註冊回調"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AWS使用futureCompletionExecutor線程池執行回調邏輯,業務邏輯使用Reactor的Mono異步編程模型(3.2.3章節介紹),所以需要將AWS的CompletableFuture響應轉換爲Mono:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nMono responseMono = Mono.fromCompletionStage(() -> {\n CompletableFuture responseFuture = sesAsyncClient.sendEmail(sendEmailRequest);\n return responseFuture;\n});"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在業務邏輯代碼中,使用Mono進行Reactive風格的異步編程。最後,由於Trip.com的RPC Framework在異步編程模型中僅支持ListenableFuture,所以我們需要將業務代碼中的MonoFuture類型轉換爲ListenableFuture類型,並返回給RPC Framework,在這裏我們使用Mono.subscribe()方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nSettableFuture listenableFuture = SettableFuture.create();\nresponseMono.subscribe(\n response -> listenableFuture.set(response),\n throwable -> listenableFuture.setException(throwable));\nreturn listenableFuture;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"④ RPC Framework註冊回調"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當RPC Framework接收到一個異步調用結果ListenableFuture後,將會通過addListener()方法註冊RPC Framework層級的回調函數:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nresponseFuture.addListener(() -> {...}, rpcExecutorService);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏RPC Framework使用了自己定義的回調線程池rpcExecutorService,即上圖中的SOA回調線程池:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nThreadPoolConfig config = threadPoolConfigProvider.getThreadPoolConfig(key);\nrpcExecutorService = new RpcExecutorService(config.taskWrapper(), new ThreadPoolExecutor(\n config.corePoolSize(),\n config.maximumPoolSize(),\n config.keepAliveTimeInMills(),\n TimeUnit.MILLISECONDS,\n config.workQueue(),\n config.threadFactory(),\n config.rejectedExecutionHandler()\n));"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,異步回調的鏈路組裝完成。等待NIO收到響應的時候,將會依次觸發上面的回調函數,進行響應流程的處理。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"response流程:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)AWS SDK Netty響應"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當netty收到IO響應數據之後,對應的EventLoop線程將會處理可讀事件並執行回調函數。EventLoop首先會讀取緩衝區中的數據並進行反序列化,而後執行channel的pipeline,將反序列化後的response傳遞給下一流程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)AWS SDK 異步回調"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AWS SDK使用1.4中提到的AWS回調線程池,進行回調邏輯的處理。AWS SDK的回調函數主要負責AWS內置的response處理,例如AWS的監控、埋點、日誌等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)業務邏輯的異步回調"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當AWS的異步回調流程完成之後,回調線程將會進入我們的業務代碼註冊的回調函數中,此時線程是1.4中定義的sdk-async-response線程。在業務邏輯的回調響應中,我們可以定義自己的業務回調線程池進行處理,也可以直接使用AWS的回調線程進行處理。由於操作非常輕量,所以在這裏我們沒有再額外定義一個業務回調線程池,而是直接使用了1.4中的線程池,減少了一次線程切換的開銷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)RPC Framework的異步回調"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如1.4所述,當業務回調邏輯全部執行完畢之後,將會觸發ListenableFuture的回調流程,此時進入RPC Framework這一層的回調邏輯處理。首先由aws-async-callback線程繼續進行同步處理,而後將會把(ListenableFuture)responseFuture中的回調函數提交給rpcExecutorService線程池處理。在RPC Framework的回調函數中,將會執行RPC的監控、埋點等功能(可參考dubbo),最終將會把異步響應傳遞給Tomcat。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Servlet3.0提供了AsyncContext用來支持異步處理請求。RPC Framework在異步請求處理開始的時候,將會通過servletRequest.startAsync()獲取對應的AsyncContext對象,此時既不關閉響應流也不進行響應的返回。當RPC Framework執行完所有的異步回調邏輯之後,此時rpcExecutorService線程將會調用asyncContext.complete()將上下文傳遞給Tomcat容器:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nfinally {\n try {\n ......\n asyncContext.complete();\n } catch (Throwable e) {\n ......\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5)Tomcat的異步響應"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"asyncContext.complete()使Tomcat容器接收到ASYNC_COMPLETE事件,在NioEndpoint.processSocket()方法中,將會通過Executor executor = getExecutor(); 操作獲取到Worker線程池(注①),而後rpcExecutorService線程把響應操作寫入到Worker線程池的events queue中,之後worker線程將響應流寫回客戶端(注②)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\npublic boolean processSocket(SocketWrapperBase socketWrapper,\n SocketEvent event, boolean dispatch) {\n try {\n if (socketWrapper == null) {\n return false;\n }\n SocketProcessorBase sc = null;\n if (processorCache != null) {\n sc = processorCache.pop();\n }\n if (sc == null) {\n sc = createSocketProcessor(socketWrapper, event);\n } else {\n sc.reset(socketWrapper, event);\n }\n \/\/ 注①\n Executor executor = getExecutor();\n if (dispatch && executor != null) {\n \/\/ 注②\n executor.execute(sc);\n } else {\n sc.run();\n }\n } catch (RejectedExecutionException ree) {\n getLog().warn(sm.getString(\"endpoint.executor.fail\", socketWrapper) , ree);\n return false;\n } catch (Throwable t) {\n ExceptionUtils.handleThrowable(t);\n \/\/ This means we got an OOM or similar creating a thread, or that\n \/\/ the pool and its queue are full\n getLog().error(sm.getString(\"endpoint.process.fail\"), t);\n return false;\n }\n return true;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,響應流寫回客戶端,整個請求-響應過程完成。"}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.2 異步線程模型總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如3.2.1所述,爲了實現異步非阻塞的流程,不僅需要Tomcat的Worker線程池,還需要引入兩個回調線程池和一個Netty的EventLoop線程池。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中一個是AWS異步SDK的回調線程池,主要負責AWS功能的處理,使用的異步編程模型是CompletableFuture;另外一個是RPC Framework的回調線程池,主要是封裝了Servlet3.0的AsyncContext並提供異步服務的能力,使用的異步編程模型是ListenableFuture。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而我們的業務代碼使用了Reactor的Mono異步編程模型,所以需要涉及不同Future類型的轉換,通過Reactor豐富的操作符,我們可以很容易的做到這一點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"預期達到的效果"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用NIO方式發起AWS的調用,避免線程阻塞,從而最大限度的消除上述BIO缺點,提高系統性能。最終使得應用符合Reactive架構理念,從而具備彈性、容錯性,以降低部署成本,提高資源利用率。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"3.2.3 NIO異步編程模型選擇"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"NIO 消除了線程的同步阻塞,意味着只能異步處理IO的結果,這與業務開發者順序化的思維模式有一定差異。當業務邏輯複雜以及出現多次遠程調用的情況下,多級回調難以實現和維護。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AWS原生異步SDK的調用模式如下,使用了Java8的組合式異步編程CompletableFuture:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\ndefault CompletableFuture sendEmail(SendEmailRequest sendEmailRequest)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e3\/e3b2625bd4c1a2c6219c70abb62f43c5.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設計NIO編碼,業界主流的編碼方式主要有以上幾種,通過CompletableFuture和Lambda表達式,可以快速實現輕量業務異步封裝與編排,與Callback相比可以避免方法多層嵌套問題,但面對相對複雜業務邏輯時仍存在以下侷限:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"難以簡單優雅實現多異步任務編排;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"難以處理實時流式場景;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"難以支持高級異常處理;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不支持任務延遲執行。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Reactive模型能夠解決上述Future的侷限。例如,使用Reactor封裝AWS的異步調用:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\n\/\/ sending with async non-blocking io\nreturn Mono\n .fromCompletionStage(() -> {\n \/\/ 注①\n CompletableFuture responseFuture = awsAsyncClient.sendEmail(sendEmailRequest);\n return responseFuture;\n })\n \/\/ 注② thread switching: callback executor\n \/\/ .publishOn(asyncResponseScheduler)\n \/\/ 注③ callback: success\n .map(response -> this.onSuccess(context, response))\n \/\/ 注④ callback: failure\n .onErrorResume(throwable -> {\n return this.onFailure(context, throwable);\n });"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"①調用AWS的異步SDK,將返回的CompletableFuture轉成的Mono。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"②如2.3所述,可以使用Mono.publishOn()將業務邏輯的回調函數放入自定義的線程池執行,也可以繼續使用AWS的回調線程繼續執行,在這裏沒有使用自定義的線程池。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③如果執行成功,則執行map()中的回調方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"④如果執行拋出異常,則執行onErrorResume()中的回調方法"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面簡單對比可以看出,相比Future,基於Reactive模型豐富的操作符組合(filter\/map\/flatMap\/zip\/onErrorResume等高階函數)代碼清晰易讀,搭配Lamda可以輕鬆實現複雜業務場景任務編排。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Reactor異步原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"reactor-core是一層編程框架,它提供的是reactive風格的編程模式,以及異步調用的編排能力。而本身並沒有真正網絡IO異步回調的功能,真正的異步回調功能是底層網絡IO框架的Future提供,比如上面AWS返回的CompletableFuture纔是真實綁定到網絡IO上的Future,而Reactor僅僅是將其包裝,方便進行reactive編程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從fromCompletionStage方法中可以找到,這裏將實際的CompletionStage包裝成了MonoCompletionStage(注①),但在實際訂閱的時候,其實是將Mono的回調函數放入了future.whenComplete中(注②),所以說Mono在這裏是CompletableFuture的外層包裝。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\nfinal class MonoCompletionStage extends Mono\n implements Fuseable, Scannable {\n\n static final Logger LOGGER = Loggers.getLogger(MonoCompletionStage.class);\n\n \/\/ 注①\n final CompletionStage extends T> future;\n\n MonoCompletionStage(CompletionStage extends T> future) {\n this.future = Objects.requireNonNull(future, \"future\");\n }\n\n @Override\n public void subscribe(CoreSubscriber super T> actual) {\n Operators.MonoSubscriber\n sds = new Operators.MonoSubscriber<>(actual);\n\n actual.onSubscribe(sds);\n\n if (sds.isCancelled()) {\n return;\n }\n\n \/\/ 注②\n future.whenComplete((v, e) -> {\n if (sds.isCancelled()) {\n \/\/nobody is interested in the Mono anymore, don't risk dropping errors\n Context ctx = sds.currentContext();\n if (e == null || e instanceof CancellationException) {\n \/\/we discard any potential value and ignore Future cancellations\n Operators.onDiscard(v, ctx);\n }\n else {\n \/\/we make sure we keep _some_ track of a Future failure AFTER the Mono cancellation\n Operators.onErrorDropped(e, ctx);\n \/\/and we discard any potential value just in case both e and v are not null\n Operators.onDiscard(v, ctx);\n }\n\n return;\n }\n try {\n if (e instanceof CompletionException) {\n actual.onError(e.getCause());\n }\n else if (e != null) {\n actual.onError(e);\n }\n else if (v != null) {\n sds.complete(v);\n }\n else {\n actual.onComplete();\n }\n }\n catch (Throwable e1) {\n Operators.onErrorDropped(e1, actual.currentContext());\n throw Exceptions.bubble(e1);\n }\n });\n }\n\n @Override\n public Object scanUnsafe(Attr key) {\n return null; \/\/no particular key to be represented, still useful in hooks\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Reactor還有另外一個好處,那就是統一異步編程模型。比如有的異步編程框架提供ListenableFuture,有的是CompletableFuture,還有gRPC、dubbo、webflux等中間件框架,都提供了自己的異步編程模型實現。如果直接針對各個框架自己的原生實現進行異步編程,將會存在不同風格的代碼。而Reactor是反應式庫的當前標準,使用Reactor庫可以封裝不同異步編程框架的異構實現,使用統一的API執行異步編程。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"四、壓測對比"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過一系列的Reactive技術改造,我們現在已經擁有了一個基於EventLoop+NIO的IO密集型應用,那麼它的性能是否如同我們的理論推導一樣將會得到提升呢?接下來我們將會通過一系列的性能壓測,得到最終的結論。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"壓測目標:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)是否能夠達到穩定狀態,以及達到穩定狀態後,系統表現和指標;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)對兩個應用在不同壓力下的指標,進行全面的對比,得出壓測結論;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下數據均爲\"穩態\"時數據,穩態定義如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/c1\/dc\/c193ac70c2188b0f6958485e4c6d8cdc.jpg","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"壓測結果"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當原應用和新應用都達到上述定義的穩態條件時,我們得到了一組對比數據。通過與原應用的壓力測試結果對比,我們發現使用EventLoop+NIO的新應用,在相同硬件資源下,QPS能夠提升2~3倍,RT縮短近50%,同時在內存佔用上也得到了一定的優化。證明該應用在經過Reactive技術改造後,性能較之前同步阻塞的Servlet架構得到了明顯提升。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"五、總結和展望"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中我們首先介紹了Trip.com消息推送平臺服務的現狀,以及現有同步+阻塞模式在IO密集型場景下的缺點。接下來我們通過分析如何解決這些缺點,引入了業界流行的reactive反應式架構。接下來在reactive宣言的彈性和伸縮性兩種手段中,總結出了EventLoop、NIO、背壓等技術手段,最後通過這些具體的技術手段來實現我們應用的升級重構。最終根據壓測結果,可以看到服務性能較之前servlet架構得到了明顯提升。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着雲原生浪潮的到來以及物聯網、邊緣計算的推進,未來應用間網絡通訊的量級將會變得越來越龐大,網絡IO會是系統架構中的一等公民。如何使我們的應用能夠具有更高的性能和更健壯的特性,以及如何降低硬件資源的成本,這些挑戰將促使應用開發人員不斷的學習實踐類似reactive相關的技術。而在學習實踐的過程中,對經典的servlet架構的優化重構一定是具有代表性意義的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在適合的業務場景下,反應式技術架構能夠有效提升服務吞吐能力,降低業務編排複雜度,幫助構建雲原生時代整體系統快速即時反應能力。但同時構建 Reactive 模式的程序也爲開發者帶來更高的要求,面臨比同步更爲複雜的編程模型,需要更好的處理好阻塞和寫出更優秀的異步代碼。希望與對反應式技術感興趣的同學和團隊多多交流。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者簡介"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"KevinTen,攜程後端開發工程師,關注Reactive和RPC領域,深度參與開源社區,對Reactive技術有濃厚興趣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Pin,攜程技術專家,Apache Dubbo貢獻者,關注RPC、Service Mesh和雲原生領域。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文轉載自:攜程技術(ID:ctriptech)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/gQYZGQVwqWF3LOH51JAOwg","title":"xxx","type":null},"content":[{"type":"text","text":"乾貨 | Reactive模式在Trip.com消息推送平臺上的實踐"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章