分佈式系統關注點(19)——深刻淺出「異步」

若是第二次看到個人文章,歡迎「文末」掃碼訂閱我我的的公衆號(跨界架構師)喲~ 
每週五早8點 按時送達到公衆號。固然了,也會時不時加個餐~

Z哥在前面的三篇文章裏和你一塊兒聊了「高性能」主題下與「緩存」相關的內容。此次和你來聊聊提升性能的另外一個大招——「異步」。html

若是你已經對「異步」有所瞭解的話,此次可讓你有更深入的理解。若是你對「異步」的瞭解比較模糊的話,此次能夠帶你一次性深刻淺出。java


「異步」有啥用?

無論咱們的思惟模式也好,仍是平時寫的最習慣的代碼,其實都是以「同步」的方式在進行的。因此,「同步」方式用着也挺好,爲啥要「異步」呢?拿你平時去買奶茶、買咖啡的例子來講說你就明白了。程序員

你應該有注意到,通常奶茶店都會分「點單區」和「取餐區」。web

圖片描述

而後你去消費的時候都是在「點單區」選擇飲料而後付錢,在「取餐區」拿作好的飲料。其實這個過程就是「異步」的,由於當營業員在作飲料的時候,你是能夠去幹其它事的,好比在邊上開一局王者榮耀或者吃雞。而他們也能夠繼續接受後面顧客的點單。sql


若是是「同步」會怎樣呢?就是你在點單區點好飲料以後,繼續排着隊乾等着營業員作好,直到營業員把飲料作好交給你以後,你就能夠走人了,他再繼續服務後面的顧客。數據庫

很明顯,若是一個店鋪裏有2個或者2個以上的營業員,用這種「異步」的方式「吞吐量「更高。編程

由於來買飲料的人時間是不規律的,可能有時候一會兒來十幾個,可能有時候半小時都不來一個。那麼經過這種「異步」的方式,雖然不能縮短製做飲料的時間,可是能夠縮短人流量大的時候顧客的等待點單時間,讓顧客能夠去作其它事。後端


其實軟件系統也是如此,現在咱們程序所在的服務器幾乎所有是多核多線程的。既然有多個「營業員」在,那麼經過「異步」的方式儘量的發揮多線程的做用,纔是物盡其用的辦法,還能提高總體的效率。緩存

不過,這事在軟件系統中要稍微複雜一些,要多考慮一下。由於線程的建立、銷燬、切換成本在不少時候甚至比得到的收益還要高。因此,只有將「異步」運用於「等待處理的時間」>「建立、銷燬、切換線程的時間」的場景下才有價值服務器

要知足這種場景的話,通常就是涉及到「I/O」處理的地方。好比磁盤I/O、網絡I/O。

好比,一旦涉及到數據庫查詢或者RPC調用的時候,若是使用「同步」的方式通訊,發起一個調用後,調用方會阻塞本身並等待整個操做的完成(想象一下執行一條耗時10秒鐘的sql)。若是使用「異步」通訊的話,調用方不須要等待操做完成就能夠返回,甚至可能不須要關心整個操做完成與否。

特別對於現在的移動網絡環境下,經過異步的方式能夠在很大程度上保證當網絡很卡的時候APP上的操做依然是流暢的,不會出現卡機。


同步vs異步

任何事物都是有利有弊的。「同步」能夠立馬知道到底成功與否(好比作奶茶的時候營業員發現珍珠沒了,立刻就能夠告訴你),而「異步」不行(這個時候你可能去別的地方溜達了)。這也致使了在「異步」環境下作「事務」的成本更高。

並且,在分佈式系統中遍及着RPC調用,若是是「同步」調用的話,還能夠配合「短連接」作到對鏈接資源的用完即放。而「異步」的話鏈接保持的時間要更長一些,至少要等到回調觸發完成後才能釋放。

圖片描述

並且Z哥還要提醒你,在使用「異步」的時候,有兩點特別容易被忽略。

  1. 發起請求的線程每每和接收響應的線程不是同一個,因此「線程上下文」是不連續的。(固然能夠經過作一些額外的編碼工做達到相似的效果)
  2. 雖然請求的順序是由客戶端控制的,可是回調的時候可能就不必定是按照請求時的順序進行的,像下圖這樣。

圖片描述

這麼看來,「同步」和「異步」均可以經過「請求/響應」模型來完成。可是,「異步」在跨進程通信中更合適抽象成「事件」來進行協做

經過「事件」進行「異步」協做的話,客戶端不是發起請求,而是發佈一個「事件」,而後期待其餘的協做者接收到該消息,而且知道該怎麼處理它,客戶端不用關心其餘協做者作了什麼,甚至也無需知道有哪些協做者存在。

基於「事件」的協做方式耦合度很低。客戶端發佈一個「事件」,但並不須要知道誰或者什麼會對此做出響應,這也意味着,你能夠在不影響客戶端的狀況下對該「事件」添加新的訂閱者。


總的來講,異步雖然能提高效率,可是仍是沒法在全部場景使用它。在實際工做中,每每咱們會同時運用「同步」和「異步」,因此瞭解清楚它們之間的區別和優缺點是頗有必要。


怎麼作異步?

咱們以一個電商APP中的「下單」場景來舉個例子。

在電商的業務場景中,下單最多見的就是如下幾個操做(順序隨便排的)。

  1. 扣減庫存
  2. 覈銷優惠券
  3. 生成訂單
  4. 生成電子發票

這些操做都是由用戶在APP中點擊「提交訂單」按鈕以後觸發的。

那麼首先來看APP這邊。通常咱們的APP僅僅負責UI層面的展現控制,業務邏輯部分都是下沉到後端的API去作的。而APP和API之間大多都是以Http或者Tcp協議的形式進行通訊的,那麼在APP層面,咱們只要藉助一些異步編程的類庫便可(這方面不是特別專業,就很少BB了)。

而後到API層面,先給全部接收請求的Action加上異步支持,java的話能夠在註解處增長asyncSupported = true,.net的話增長aysnc關鍵字。如此一來,就是告訴程序所在的宿主(web server或者service)「我這個方法是支持異步的,你接收到請求以後就不要阻塞了,去忙別的吧」。


接下來就輪處處理上面提到的電商下單場景中的4個操做了。理論上,這4個操做能夠所有按「請求+回調」的異步模式進行,徹底可行。這個過程其實有點像「並行」的意思,最終的處理完成時間是由最晚完成回調的那個操做決定的。

圖片描述

可是,爲了不個別程序的意外狀況致使最晚回調的時間被拉的很長,咱們就須要來考慮一下,那些無需即時知道甚至無需關心返回結果的操做能夠經過「事件」的形式進行「異步」。

圖片描述

好比,像「生成電子發票」這種操做,對當前這個業務場景來講並不須要實時知道它的返回結果。

雖然咱們知道它的業務邏輯相比生成訂單這些更簡單,處理起來很快,可是一旦服務出現問題,那就很差說了。

題外話:網絡是不可信的,由於它容易受到攻擊、不穩定,因此在分佈式系統中這些「意外狀況」格外常見。多一個硬性的依賴,就多一份出錯的可能性。


若是沒有作好前面一些文章中提到的「高可用」保障(文末放傳送門,感興趣的能夠看完這篇再去看)的話,一旦所依賴的服務出現問題就會被拖累,致使接收到最晚回調的時間拉長,甚至因爲未能及時回調回來致使當前的處理沒法繼續下去。

那像這樣的業務點,咱們就能夠經過「事件」的形式進行「異步」處理,好比在生成完訂單以後發出一個「訂單被建立」的「事件」,而後由訂閱該「事件」的「生成電子發票服務「接收該「事件」並進行處理。如此一來,即提升了「提交訂單」時的處理效率,還使得「電子發票服務「的任何波動都不會影響到「提交訂單」操做的正常進行。

圖片描述

對這個「事件」的處理,你能夠在程序中創建一個單獨的方法進行,它的入參是一個「事件」基類,返回值是void。具體的「事件」數據你能夠選擇持久化到DB,也能夠選擇投遞到MQ中。大體是下面這樣的代碼

void SendEvent(BaseEvent event){
//投送到DB或者MQ;
}
​
​
class BaseEvent{
DateTime OccurredTime;
}
​
class OrderCreated extend BaseEvent{
Order order;
Invoice invoice;
...
}

可能你會問事件處理失敗了怎麼辦?甚至作持久化和投遞到MQ的s之後就異常了咋辦?能夠轉去看以前的文章《分佈式系統關注點——「共識」的兄弟「事務」》,以及文末的高可用系列文章。


最後,當你在使用異步的時候,還有一項工做要作,雖然是輔助性的,可是很重要。

就是須要引入一個全局惟一標識將整個異步的請求鏈路「串「起來,不然排查問題的時候夠你頭疼的,徹底分不清楚哪是哪。若是條件容許,能夠再引入一個日誌聚合系統。好比ELK全家桶,讓你能夠更高效的篩選日誌信息。


總結

好了,咱們一塊兒總結一下。

此次呢,Z哥先和你聊了下「異步」的意義,以及它是如何來提高性能的。

而後和你聊了一下「異步」的一些弊端和常見的運用方式。

最後以一個電商下單的例子梳理了一下作「異步」的思路。

但願對你有所啓發。

圖片描述


相關文章:


做者:Zachary

出處:https://www.cnblogs.com/Zacha...



若是你喜歡這篇文章,能夠點一下文末的「」。

這樣能夠給我一點反饋。: )

謝謝你的舉手之勞。


▶關於做者:張帆(Zachary, 我的微信號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。歡迎 掃描下方的二維碼~。
按期發表原創內容: 架構設計丨分佈式系統丨產品丨運營丨一些思考

若是你是初級程序員,想提高但不知道如何下手。又或者作程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注個人公衆號「跨界架構師」,回覆「技術」,送你一份我長期收集和整理的思惟導圖。

若是你是運營,面對不斷變化的市場一籌莫展。又或者想了解主流的運營策略,以豐富本身的「倉庫」。歡迎關注個人公衆號「跨界架構師」,回覆「運營」,送你一份我長期收集和整理的思惟導圖。

圖片描述

相關文章
相關標籤/搜索