許多場景下爲了更迅速的響應客戶端的請求,將問題轉化爲實時反映業務狀態的變化,能更好地提高用戶體驗以及支撐更大量的用戶請求,因而催生了響應式編程,本期跨境茶話會仍舊邀請了中美兩地的相關專家來談談響應式編程的應用。前端
全網首發·技術乾貨·大咖對話react
整理:魔窗丨 7728字丨15分鐘閱讀web
來源:本文整理自跨境茶話會線上分享,爲魔窗(magic-window)原創首發,轉載需得到受權算法
【主持丨大師兄】:這期爲何要選響應式編程這樣一個主題?由於前兩期咱們作了一個性能優化方面的主題和微服務方面的主題。性能優化本質上會解決高併發狀況下同步延時的一些問題,可是可能另外有一些業務和架構上的模式,採起一些消息驅動的方式能夠解決後端即便不能實時處理相應,也能夠低延遲的保證客戶端體驗的場景。這樣的場景就是響應式編程的系統架構。首先介紹一下這期的嘉賓,首先是徐鵬程,他目前任職於谷歌,是負責廣告技術方面的專家。數據庫
【嘉賓丨徐鵬程】:你們好,我是徐鵬程,如今在上海谷歌。我在谷歌上海作了大概七年的時間,前六年我都是在作廣告優化的一些工做。最近這一年我開始作安卓的開發。在谷歌上海以前我還在谷歌北京本部也待過一段時間,中間也出來創過業。編程
【主持丨大師兄】:接下來我介紹一下童奎鬆,他如今在Snapchat,負責Snapchat數據方面的工做。後端
【嘉賓丨童奎鬆】:我叫童奎鬆,2002年浙江大學畢業以後,我在國內寫了差很少十年的代碼,在2012年加入LinkedIn,主要負責和數據分析相關工具的開發。今年年初加入到Snapchat,作數據分析相關工具的開發。緩存
【主持丨大師兄】:接下來我介紹一下徐焱飛,他如今是磐石科技的CEO,磐石科技也是一款投顧金融平臺的產品,他自己在一些CQRS,Eveint Sourcing等相關技術方面很是有經驗。性能優化
【嘉賓丨徐焱飛】:你們好,我是徐焱飛。我最先在Wipro作零售的一些跨國項目。後來加入愛立信上海研究院,一直作通訊方面後臺的一些工做。以後加入普蘭金融在作分佈式後臺的開發。今年如今開始出來創業,是一個投顧平臺。網絡
【主持丨大師兄】:接下來咱們進入這期茶話會第一個環節,首先想請各位嘉賓談一下關於他們在工做中關於響應式編程的實踐。
【嘉賓丨徐鵬程】:我在谷歌內部假如如今從事的安卓開發,咱們在響應式編程上會有更多的實踐,在以前的廣告優化的地方,由於我長期是在作後臺的工做,咱們反而在這方面作的實踐相對少一點。可是安卓這邊由於涉及到不少前端的交互,因此咱們在編程上的實踐更多一些,由於應用的場景會更多一些。
【主持人丨大師兄】:能不能舉一些大家在具體工做中用到的響應式編程的業務的場景和技術方面實踐的經驗呢?
【嘉賓丨徐鵬程】:好比我最近在作APP的時候,裏面有一個常見的webview控件,他是一個事件驅動型的方式,好比你能夠給它加載和定製化一些事件client,好比這個webviewclient上你能夠作一些事件處理。可是,這個有一個比較很差的地方就是事件處理client是很難被重用的,由於兩個不一樣的webview上面可能有不一樣的事件處理方式。可是它的業務邏輯可能80%是相似的,20%是不同的,可是你沒有辦法重用這80%的代碼,除非你作一個級層的關係,那這樣兩個徹底不相關的webview可能就會存在一個依賴關係。咱們並不想要這樣的耦合方式,因此咱們把wenview的事件徹底改形成了observer模式的這種方式。因此每一個事件咱們均可以把事件相對應的observer提出來,這樣的話最終在不一樣的webview的狀況下咱們就能夠把80%相似的observer組裝在一塊兒,經過這樣的方式來解耦,使得這個代碼可以獲得重用,也比較易於管理。
【主持人丨大師兄】:謝謝鵬程,鵬程剛剛從一個客戶端的角度幫咱們介紹了怎麼樣經過一些響應式編程,就是事件驅動的方式把真正的事件處理的邏輯給解耦。接下來奎鬆幫咱們介紹一下吧。
【嘉賓丨童奎鬆】:我最近在作一個項目,Snapchat對user ID其實管得很嚴的,當時在咱們全部處理的數據裏面,全部的僱員均可以看到User ID的,因此咱們想把它轉化成所有用一個叫自動生成的Ghost ID來替代全部的user ID,在咱們分析的數據庫裏面。而後這裏咱們就要作一個系統,讓全部其餘第三方系統能經過User ID查到對應的Ghost ID,而後把User ID換掉。它其實就是一個User ID和Ghost ID的對應關係,已經在數據庫裏面有了。可是由於它的吞吐量很是大,基本上全部的由於是事件都會有user ID,天天的事件基本上在T的量級。因此,爲了應付這麼大量級的查詢,咱們在前面作了一個cache。但這就是一個典型的高吞吐,它基本也沒有什麼CPU預算,基本是高吞吐、高IO的運用,因此咱們就想到用響應式編程來解決它。其實咱們在這裏用響應式編程主要是解決的由於它大部分都是IO,因此咱們不想把IO等待來佔用CPU的時間。這樣的話,同時咱們就採用了全譯步非阻塞的方式來響應請求,這樣基本在單臺機器能夠達到100K以上的吞吐率。
在這以前,我在LinkedIn的時候,整個系統都是基於事件驅動的,它是基於Kafka架構上的事件驅動的響應式系統,這個對系統來講是同樣的,就是全部的編程全是異步。我先不說這個異步編程在整個應約上帶來的高吞吐率、低延遲,其實延遲不會低,就是容錯率有一個好處,另外帶來一個好處,從咱們數據分析來講,由於全部業務中的事件,因此你在作業務分析的時候只要基於事件來作,其實你能解決大部分數據分析的問題。這是我在這兩個公司用響應式編程的一些經驗。
【主持人丨大師兄】:剛剛談到在LinkedIn上大家全部的數據分析都是用事件,用一些事件無非就是一些狀態的改變,按照這樣的模式去作,你能舉個具體的例子嗎?
【嘉賓丨童奎鬆】:我舉個例子,好比最典型的PageView的事件,好比個人任何一個LinkedIn的用戶查看個人profile頁面,就有一個profile的PageView事件。可是這個Page view 的事件發到Kafka,它會返回個人相關信息,而後生成Profile頁面。同時這個事件由於它是發的Kafka的,因此Kafka能夠把它存到HDFS上,而後咱們作數據分析的時候只要從HDFS把這個PageView的事件拉出來,咱們就能算出當天的每一個頁面的PageView是多少。
【主持人丨大師兄】:大家如何對這種事件作一些抽象的?由於可能大家定義了一個具體的狀態,可是真正處理這個狀態變化的不只僅一個業務模塊,還會有一些其餘業務上的模塊會針對這個狀態變化作處理。
【嘉賓丨童奎鬆】:咱們在總體設計的時候專門有一個committee來統一地審查這個事件,也就是一個團隊你能夠說我要這個事件那個事件,committee會去審覈你這個事件是否是已經存在了,和之前的事件是能夠重用的,若是不能夠單獨的話,那你的事件裏面須要哪些屬性。你要兼顧到業務須要和數據分析須要。由於事件進了Kafka以後,Kafka能夠分發到處處,每一個系統只要監聽一樣的事件,它能作不一樣的業務。
【主持人丨大師兄】:你還有什麼要分享的嗎?
【嘉賓丨童奎鬆】:這裏有一個事情我要澄清的,從我本身的理解來講,咱們這期的分享題目叫響應式編程,可是配圖其實從個人角度來講是響應式系統的圖,其實它是整個系統。因此我會發一個連接:https://www.oreilly.com/ideas...,這個連接裏面有詳細的人家寫的響應式編程和響應式系統的區別,在個人理解來講,響應式編程基本上屬於你在一個process裏面怎麼用異步來高效地使用你的線程,也就是讓你的線程在你的任務之間切換會不多,你的CPU切換不多。響應式系統就涉及到你怎麼去用事件驅動或者消息驅動來構造你的系統,你的系統須要很好的用戶響應,還要容錯,要有彈性,在高負載狀況下也要有彈性。因此,從個人理解上響應式編程和響應式系統實際上是兩個概念。
【主持人丨大師兄】:其實響應式編程是包含在響應式系統裏面的?
【嘉賓丨童奎鬆】:對。
【主持人丨大師兄】:我這邊也說一下,此次咱們整個的主題其實應該是響應式系統,而不是響應式編程,可能名字上會有有點誤導你們。接下來徐焱飛你這邊介紹一下吧。
【嘉賓丨徐焱飛】:好的。我很是贊成剛纔奎鬆說的關於響應式編程和響應式系統的區別。就在本次交流前,剛和申竣線下溝通了一下,由於這個地方確實是要澄清一下。我以前的工做經歷,有一個項目其實和奎鬆有點接近,也是用全異步式的用Kafka將全部的請求聚合起來,而後用storm對流數據進行處理,一方面是作一些大數據的分析,一方面是把這些數據進行落地。我介紹一下基於事件驅動的CQRS架構通常所適合的場景。好比咱們作傳統的系統當中,拿電商舉例,若是我如今發了一個訂單出來,這個訂單落地,若是咱們只關心訂單的變化,好比訂單的數量發生變化,那是一個很是簡單的系統,常規的咱們用CURD的系統就能夠解決。可是如今這個電子商務的時代,可能會遇到兩個問題,第一個問題是高併發,且讀大於寫。若是咱們這個時候往數據庫作簡單的CURD的時候可能就承受不了。這個時候就得引入各類緩存、讀寫分離、分庫分表之類的技術,CQRS其實算是從應用級開始的一種讀寫分離。第二個問題,是需求變化的太迅速以致於咱們難以推測系統在後面會如何演進。好比咱們如今最開始只是訂單,那麼後面可能對這個訂單數據有一個延伸,好比我去減庫存,或者從這個訂單產生的後面有支付,紅包等等,可能會延伸出更多的業務流程。在這種時候按照傳統的CURD的系統,咱們函數式的往上去拼接各類代碼,無心就顯得效率比較低。除了採用微服務外,使用CQRS用相似事件流處理的方式去實現,能夠避免業務系統間相互影響,相互隔離的同時,也能爲將來的各類擴展作好準備。因此我如今最新的項目就是採用基於CQRS,加上EventSourcing,對整個的系統一個是知足讀大於寫的高併發,第二個是知足咱們將來對系統的快速調整和演進。
【主持人丨大師兄】:感謝各位嘉賓介紹了在工做當中的一些響應式系統的經驗。接下來第二個環節咱們會討論一些問題,這些問題都是在作響應式系統當中會出現的比較細節的業務或者是技術方面的具體的問題。爲何會出現問題呢?可能你在作一些同步的方式當中是不會出現,作一些事件驅動,包括像高併發,異步處理就會衍生出這些問題。個人問題是在一個響應式系統中,可能你整個的端到端的業務場景中間會通過不少的事件處理的環節,若是某一個環節出了問題,我想問一下各位嘉賓,如何去作一些事務的回滾,不知道各位嘉賓在這方面有沒有相關的一些經驗。
【嘉賓丨童奎鬆】:我先回答吧,當你作整個反應式系統的設計的時候,其實大部分狀況下你應該結合DDD,領域設計驅動來作你的設計。你的數據其實在某種狀況下是有邊界的,就剛剛你說的,若是有一致性要求的話,事務應該是在某一個模塊或者某一個服務的範圍以內,它不會跨多個渠道,就是分佈式事務,這是在反應式編程裏面忌諱的東西,是不該該採用的。因此在單個叫application也好,或者叫服務也好,在單個服務裏面你能夠用你的事務來保證你的數據的一致性。由於無論是任何數據你總會有一個擁有者,好比說訂單系統,全部和訂單相關的信息都會屬於這個訂單系統。因此當你對訂單作任何變動的時候,它的一致性是由訂單系統處理的。當訂單下單以後他要去預留庫存的時候,這時候他會去發個消息給庫存系統,而後由庫存系統去預留這個庫存。因此剛剛你說的,從個人理解來講,它的跨系統之間的事務失敗,這種狀況是不存在的。
【主持人丨大師兄】:你的意思是說其實從一些業務設計的角度去講,沒有一個端到端的事務失敗的概念?
【嘉賓丨童奎鬆】:當你作領域驅動設計來講,每一個系統就是獨立性的,是孤立的,沒有端對端。好比端對端只是你整個訂單系統或者單獨庫存系統,但沒有說我一個訂單下去到保留庫存,到保留庫存,到付款,到交貨,這是整個端對端的失敗。
【主持人丨徐鵬程】:我能請教一個問題嗎?剛剛您說的訂單系統裏面,好比我在生成訂單的過程當中須要減小庫存,對於庫存系統來講那就是一個完整的事務,由於這個庫存的事務是發生在訂單操做的過程當中的,若是最終這個訂單失敗了,那麼庫存那邊的事務是否是要回滾呢?
【嘉賓丨童奎鬆】:這看你怎麼設計,個人設計方法是這樣的,下訂單的時候其實你不須要預留庫存,爲何呢?由於從某種角度上來講,整個反應式系統裏面最重要的一個概念是最終一致,而不是你要實時一致。也就是說即便我下定單的時候沒有庫存又會怎麼樣呢?最多我下了訂單以後我再去發個消息給庫存系統說你給我留一個庫存,這時候庫存系統若是沒有庫存的話他會返回一個好比說我沒有庫存,那訂單系統讀到這個沒有庫存的事件以後就會把本身狀態改爲backordered或者別的狀態。也就是說下單的過程當中我是不須要和庫存有這種分佈式事務的交互的。
【嘉賓丨徐鵬程】:若是我這個訂單除了庫存以外還依賴其餘的角色,好比依賴於付款,比方庫存說我有,而後付款說最終失敗了,那麼你仍是須要再去讓那個庫存恢復過來是嗎?中間這個狀態怎麼處理?
【嘉賓丨童奎鬆】:狀態表示一致是最終的一致性,它是最終的一次性,不是我這樣一個時刻下所有表現持續。好比你剛剛說的,我庫存已經留了,我如今去付款,而後付款失敗了,而後就會有一個付款失敗的消息發出來。這個時候庫存和訂單都會監聽這個付款失敗的消息,庫存監聽到付款失敗的消息,庫存回滾,訂單監聽到付款失敗的消息訂單的狀態變成付款失敗。
【嘉賓丨徐焱飛】:這裏我來講幾句吧。關於這個地方,首先須要明白一個問題,爲何會有分佈式事務這個問題。若是說咱們沒有分佈式,傳統的好比下訂單,到保留庫存,到支付的流程,常規的理解確定是一個事務。後面一個環節必須等到前面一個環節成功,那才能進行到下一步。好比說若是下訂單的時候這個商品原本就沒有庫存了,那這時不可能就支付它,買家不可能去買不存在的東西。可是由於壓力的東西,咱們要到分佈式上,一旦產生了分佈式,狀態就會落在不一樣的節點上,這個時候問題就變得複雜了。
這就是咱們業界內一般來講的CAP理論,所謂的CAP理論這裏稍微普及一下,它是指分佈式系統的一致性、分佈性和容錯性,這三方面是不可能知足三者的,最多隻能知足其中二者。因此通常業界內在分佈式事務上有兩種作法,第一種是TPC,兩段式提交,就是淘寶以前最經常使用的作法,好比這三個階段每一個階段都嘗試着向數據庫作一次寫入,先各自嘗試把當前資源給預留下來。當三個都OK了,固然這中間是有另一個第三方的協調者,好比像JBoss他們作JTA這樣的事務實現的時候就會有一個第三方的監聽,保證在三個階段的提交都確定成功了,他再作第二次的提交,把三個對應的資源真正的commit掉,從而最終同步更新狀態,這就是兩階段提交大體的過程。
還有一個作法就是剛剛奎鬆說的最終一次性,最終一次性在DDD領域設計當中,一般來說咱們是這樣作的。整個大的流程這一個環節,咱們常規的理解是叫作一個事務。可是,由於咱們把它分紅好比這三個服務的話可能會落在三個獨立的領域、獨立的服務當中去處理,就像如今微服務可能就變成了三個服務的節點,每一個服務節點各自的處理過程是一個事務,好比下一個訂單,到數據庫保存成功,那這個事務就算成功了。
那整個流程怎麼保證呢?在領域當中咱們有一個概念叫Saga,Saga能夠用來保證整個流程是按照設計好的流程走,好比下訂單是成功的,可是去保存庫存的時候可能失敗了,這個時候就能夠發出來一個庫存失敗的消息,其餘的幾個節點就能夠監聽到這個消息以作出不一樣反應,例如訂單服務就能夠將訂單狀態改成下單失敗。
說到這裏不得再也不囉嗦幾句,關於領域設計當中咱們通常說到聚合狀態的更新,其實已經牽扯到了Actor模型了。在領域設計當中,咱們把具備相關聯關係的一些數據單元用Aggregate聚合來表示,若是說要找一個類比便於理解的話,就相似於咱們OO編程中的一個大對象。好比訂單,常規咱們去更新訂單的時候,在傳統的系統當中咱們能夠前面發起一個函數調用去更新這個訂單的狀態。可是在領域當中,好比Actor模型中,它實際上是經過一個對外的事件接收器,外面的人不可以直接更改聚合內部的狀態,必須是經過事件,你發一個事件告訴我你要幹什麼,而後至於怎麼幹是整個聚合根內部本身來作的一件事,這就是一個Actor模型。
因此剛纔提到了在整個過程中的異常處理體系,這當中的整個異常處理體系其實分兩部分的,一部分是聚合根內部的體系,這個時候它的異常咱們能夠用常規的手段來解決,好比若是發生了異常也好,或者寫入失敗也好,這個時候咱們能夠發出一些事件發給外部,交給外部一些對這個事件有興趣的訂閱者。
還有一種是咱們整個事務的,整個體系的,像剛剛那個流程從建立訂單到保留庫存,到支付,整個環節實際上是靠聚合以外的,好比Saga的這種方式來進行保證的。
【主持人丨大師兄】:我這邊總結一下,徐焱飛和奎鬆說的,我若是在領域和領域之間其實沒有所謂事務的概念,全部的東西都是基於狀態變化的處理,整個領域只是負責接收外部領域的消息,作一個相應的處理。
【嘉賓丨徐鵬程】:對的。其實我這邊還有一種狀況就是我以前作系統的時候咱們有一個索引系統,就是咱們對存儲的數據作了一個索引,那個東西也是事件觸發的,可是那個東西咱們認爲是一個比較弱的輔助系統,因此咱們不強行保證它的最終一致性。若是更新失敗了那也就隨他去了,由於對於咱們整個系統來說不是必需要求。因此這種狀況下咱們一般的作法,若是事件處理的時候失敗了那就不去管它。可是咱們另外還作了一個offline的re-indexer,也就是天天它會從新把全部的數據從新索引一遍,保證它在必定時間以後數據仍是正確的。
【嘉賓丨童奎鬆】:對,這個就涉及到當你的領域系統裏面處理一個消息的時候若是失敗了怎麼處理,失敗了其實有各類方式,一種就像你說的我直接把消息丟掉,由於我失敗就失敗了,我不在乎這個。好比說去寫log,我若是log寫不進去,我失敗了就把它丟掉。另一種就是我重試,我會按期重試,或者各類算法按期重試,或者我有多臺機器的話我換一臺重試。第三種就是剛纔你提到的,我可能不重試,我也不把它丟掉,我把它放到一個錯誤消息的隊列裏面,定時好比天天或者每小時,定時對錯誤隊列裏的消息再重試。
【主持人丨大師兄】:剛剛的問題是關於事務的,我看你們把異常處理的問題也討論了,和事務是相關聯的。因此接下來的問題是響應式系統是創建在事件驅動之上的,那這個問題就是當事件愈來愈多, 公司的系統愈來愈大的時候,因爲公司的組織架構,可能我研發一個工程師或者一個小工程的團隊只是負責兩三個事件的處理,就是兩三個領域,兩三個事件的處理。可是可能做爲我總體的系統架構或者是大的產品經理,極可能面對的是整個事件處理的流程有成百上千個狀態。其實,我做爲更大的一個架構層面或者產品層面的角度,可能有架構師須要瞭解全部的成千上百的事件處理的狀態過程究竟是怎麼樣的。我在設計的時候,或者編寫代碼的時候,整個在存在成千上百個事件的過程當中,我怎麼樣瞭解到整個事件處理的全貌,使我當中要作一些修改的時候不至於發生任何的問題。這個我想請各位嘉賓發表一下本身的意見,怎麼樣管理這麼多的事件?
【嘉賓丨徐鵬程】:首先,你剛纔提到了,若是你是一個複雜系統的話,不是說必定,可是百分之八九十必定推薦你要用領域驅動設計。當你作領域驅動設計的時候你會把整個大的系統分紅不一樣的領域,好比分紅十個領域或者八個領域,簡單一點來講就是咱們剛纔說的網上訂單你會分紅訂單、庫存、付款三個領域。你的業務是有流程的,好比下訂單你是先下訂單再去保留庫存,再付款,再發貨。
可是咱們剛剛也提到另一個事,你的領域之間的交互只能經過消息來處理。你把這個結合起來,你的流程就是你的消息,這樣的話當你把你的領域畫個圖,而後它們之間的鏈接就是消息,這個消息是決定你的流程的。好比我做爲訂單的流程第一步是下單,在訂單系統裏面發生,而後訂單會發一個事件說下單成功,這個時候庫存會監聽到下單成功這個消息,而後再作保留庫存的動做,而後保留庫存成功的時候會發一個消息說庫存保留成功,而後再是付款系統。而後付款會有成功和失敗兩個不一樣消息。若是成功了以後會進入發貨系統,回到庫存以後發貨。
因此,剛纔你說怎麼去肯定我有哪些消息,我哪些消息是必要的,哪些消息是沒必要要的,哪些消息是重複的。這其實就由設計的時候你的業務流程決定,由於消息就是你的業務流程當中的一步,你沒有這個消息,你業務流程走不通。當你的業務流程不須要步驟的話你也應該不存在有單獨的消息。這是個人理解。
【主持人丨大師兄】:我這邊延伸到了一個問題,剛剛講到可能從業務上來說我是須要作這樣一個設計,可能就是畫一個比較大的流程圖,或者是用文檔的方式把他們畫下來。可是有一個問題是在於,在你真正作一個開發的時候,可能分好幾個階段,你在開發的時候需不須要我這邊全局地去維護一張好比XML描述文件也好,這樣一個存在我全部的一個消息處理,全部一個工做流的文件,讓我整個工程師可以去到單一的文件上去找相應的流程步驟,第一個是開發過程中。第二個是我整個系統上來之後有沒有一些比較好的監控的手段,可以讓我清晰地看到如今每個事件處理,每個領域每個事件處理的狀態會有沒有什麼異常,我是否是須要進行一些人工的干預?
【嘉賓丨童奎鬆】:第一個是否是要一箇中心,一個地方放全部的事件,這個我我的是不建議的。由於每一個模塊是獨立的,每一個模塊負責處理本身的事件,它發哪一個事件,好比庫存、訂單,訂單成功以後庫存系統會關心,這些事是由每一個人本身決定的,而不是我由一箇中心的地方來放全部的事件。怎麼肯定我有哪些事件呢?你要有一個流程圖,每一個領域之間的交互關係是怎麼樣的,在你作領域設計的時候,作系統設計的時候你的這個文檔是須要的。
第二個就是剛剛說的監控,由於全部的事件其實你會有一個地方去存儲的。好比說Kafka,這時候你事件處理的成功與失敗徹底能夠經過監控kafka來肯定。若是你的流程設計得好的時候,當某個事件處理失敗的時候你會把它放到一個錯誤隊列裏面,或者你會有必定的標準說這是執行失敗了或者執行成功了,你能夠監控kafka裏面隊列的狀態,或者監控你的metric系統來決定。好比我一個事件成功了,我會往個人metric系統裏面發一個+1,成功+1或者失敗+1,或者嘗試+1,這取決於整個系統怎麼設計。
【主持人丨大師兄】:我這邊理解了,其餘兩位嘉賓有什麼想法?
【嘉賓丨徐鵬程】:首先我以爲你剛剛說的咱們可能有成千上萬個不一樣的事務,不一樣的狀態,可是一般來說是能夠分模塊的,實際上未必可能真的有那麼多狀態。由於比方我有一千個狀態,可是可能這一千個狀態分別屬於十個不一樣的領域,可能有80%是內部的狀態,並不必定暴露到外面去,因此你可能最終畫的那個圖我以爲最終不該該是我這個狀態互相的轉換關係,而是我這十個領域之間有哪些依賴關係,可能80%是在某個領域以內的。這是第一點。
第二點,其實在我本身看來,由於以前我負責那個項目咱們也是能夠認爲是一個領域,咱們其實並非太關心調用者或者使用者之間的狀態,由於這也是他們的事情,咱們只關心第一咱們內部的狀態,第二咱們暴露出去服務的狀態。全部的東西咱們都會有log,咱們一般是會知道誰在使用咱們某一個接口,最終比方咱們須要在技術上改進這個接口,或者須要改接口的參數的時候咱們一般會看誰用了這些東西,它是否會跟咱們新的接口有問題,我會通知咱們的調用者你的這個東西可能須要進行怎麼樣的修改才能符合咱們調用的正確性,可能從何時開始咱們會改變調用的接口。只有在這個狀況下,咱們纔會對咱們上下游的狀態有更多的溝通,不然咱們只要保證對外的接口不變就能夠了,對內的狀態和事件咱們不認爲咱們的上下游接口是應該知道這些東西的。
【嘉賓丨童奎鬆】:這裏我補充一點,剛剛談到的是調用接口,其實這仍是原來的http或者rest調用,或者RPC調用這種同步方式,或者說它不是事件驅動的。真正當你事件驅動、消息驅動的時候,其實你消息的內容和消息的類型已經決定了它是要作什麼事情。當你作升級的時候其實你要作到一個上下兼容,大部分狀況下只是你的消息裏面的內容只增不減或者不改,其實你就能作到向下兼容。
【嘉賓丨徐鵬程】:對,可是有具體狀況,好比咱們碰到的狀況這個消息你不會再支持了,你發過來也沒有用,咱們沒有這樣的功能了。
【嘉賓丨童奎鬆】:這個是這樣的,你接不接消息取決於你,可是發不發消息取決於咱們,我在反應系統裏面發消息那一方面爲主的,我把個人消息發出去,你收不收,你作什麼事情我根本管不了,這可能調用方式不同。
【嘉賓丨徐鵬程】:實際狀況下你一般發一個消息過來,你多是但願我處理完以後生成一個新的消息出去,可是像這種狀況下咱們以前會反饋回去,可是如今由於某些緣由咱們再也不有這樣的反饋了,其實會影響到你的一些東西。
【嘉賓丨童奎鬆】:這個就看你怎麼設計,從個人角度來講我發消息的時候我根本不關心誰來處理不處理。我關心的是好比下訂單那一塊,我訂單生成以後我有一個訂單成功的消息,我根本不在意處理不處理這個消息,我只等下一個庫存預留成功這個消息,我只要等這個消息,我無論你聽不聽。因此,我決定須要當我作訂單系統的時候,我須要關心你付款成功不成功這個消息,你庫存成不成功這個消息,我不關心個人訂單生成成功這個消息發出去以後到底有多少人去處理,或者說你今天處理,明天不處理。
【嘉賓丨徐鵬程】:可是一般狀況是這樣的,好比你是訂單系統,我是庫存系統,你發一個消息給我說要求我預留這個庫存,可是好比我從如今開始當機了,我既不發出去一個預留成功的,也不發出去一個預留失敗的,那在你那邊的流程不就work了嗎?
【嘉賓丨童奎鬆】:首先個人訂單成功以後我不是發一個告訴庫存你要預留這個事件,我發的是訂單成功事件。庫存那方是監聽我訂單成功事件來決定我保留庫存成功不成功。就是訂單系統自己不知道你庫存是要作保留庫存仍是直接去發貨,訂單系統不在意。庫存系統拿到訂單建立成功這個消息以後他本身來決定我是要保留庫存仍是直接發貨。這個理解我以爲反應式編程要反過來的。
【主持人丨大師兄】:奎鬆的觀點是說,我全部發的消息都是基於狀態的變化,不是我告訴你作什麼事情,是我告訴你我這邊狀態發生了哪些變化?
【嘉賓丨童奎鬆】:對的,我告訴你我作出了哪些變化,或者說我告訴你,這個其實就像他們會計記帳同樣,會計記帳你告訴個人時候你就來報銷,你告訴我今天吃飯一百塊錢,我無論你吃了什麼,無論你怎麼吃,我只記錄一點,吃飯一百塊。
【主持人丨大師兄】:焱飛對這個問題有什麼見解嗎?
【嘉賓丨徐焱飛】:我很是贊成奎鬆剛剛說的,由於在領域驅動裏面其實每一個聚合根本身是經過事件驅動的,一般它不會去管其餘領域的狀況,只管本身。說白了,有什麼樣的事件進來,而後聚合根內部邏輯根據這個事件進行處理,而後對外發出相應的事件,就是事件傳遞驅動的過程。可是從系統設計的角度來講,可是像鵬程剛剛講的,有的時候會遇到宕機,這實際上是屬於運維級別的東西,咱們要對這個信息瞭解。一般的作法除了作自啓外,剛剛奎鬆也講了,一個是記log,一個是metics,若是用記log的方式咱們通常會在每個事務的發起點,產生一個全局的ID,這個ID會帶在每個消息中。好比,上面的例子,從建立訂單開始的時候就能夠產生一個事務級的ID,那這個ID在訂單建立成功後隨着建立成功的事件發出去,那庫存系統在收到這個事件以後依然會把着這個ID帶着往下游或者其餘的地方發出去。這個時候不管是日誌系統也好,仍是metrics也好,咱們去查找這個事件的時候能夠很容易地經過這個惟一的ID,把它上下游全部的事件串起來,這樣後臺的管理員能夠看的到,好比像剛剛說的支付系統宕機了,那這個時候能夠很明顯的看到,同一ID的事件流會存在大量的更新庫存以後,後面就沒有支付了。像在SpringCloud全家桶裏面有一個SpringMetrics的集成或者SpringBootAdmin裏面就有相似的圖形界面,能夠很清晰地在裏面看到每個流程走到每個節點的狀態,我以爲這個狀況下能夠快速地發現問題。
【嘉賓丨童奎鬆】:這個徹底贊成,原來在Linkin系統的時候也是,在你的業務流程的最開始的步驟會生成一個UUID,這個UUID會帶到你整個業務流程的每一步。
【主持人丨大師兄】:各位是談了一些監測方面的時候可能要有一個像奎鬆說的UUID,這個UUID負責端到端的業務鏈過程的監控,這是上線之後的監控過程。可是我順便引入下一個問題,由於剛剛鵬程說的,實際上你即便保證你每個領域,每個事件處理的模塊,在本身的範圍內都測試經過的狀況下,可是做爲我整個的測試環節,針對我整個測試工程來說我也不能保證你有這麼大的事件處理業務是徹底正確的。因此整個開發管理以後你仍是要通過端到端的測試過程的,可是這樣的測試過程確定和傳統的方式有很大的區別的,我想問一下各位,在最後整個系統開發完以後你的上線測試期,我想讓各位分享一下在測試過程當中有什麼經驗,有什麼和傳統的方式不一樣的地方是須要注意或者是須要花時間的。由於即便你各個模塊測試充分了,可是從工程上來說我仍是要把整個連在一塊兒的集成作測試的。
【嘉賓丨徐焱飛】:我以爲這個是不衝突的,這個東西已經跟咱們說的領域系統也好,或者是事件驅動的系統也好,關係不大。包括咱們如今最流行的微服務,也要處理這樣的問題。首先把每一個節點系統本身的測試作掉,由於這個很是簡單,其實只要花時間進去就行了,能夠用窮舉的方式把全部的事件傳進去,看看它的output是否是咱們想要的。把這個作掉,而後整個大環節的集成測試其實就跟傳統的測試沒有什麼區別,只要在源頭髮出一個請求,好比像CQRS當中先發出一個command,而後去query出一個結果就行了。若是這個結果不是咱們想要的,那就說明這個環節過程中是出了問題,而且對一些異常的狀況咱們也要去作相應的測試和處理。
【主持人丨大師兄】:在用傳統的方式我這邊一個負責測試的,出了一個問題,我立刻知道去找哪一個負責人,像這種狀況的話個人測試人員找誰,他其實是不知道的。
【嘉賓丨童奎鬆】:其實你是知道的,咱們剛剛說了,你的業務流程有個UUID,你用這個UUID就能夠查到你事件的列表,其實你就知道你應該在哪一步事件沒有發出去,或者哪一步事件出錯了,你就應該知道找誰。
【主持人丨大師兄】:因此是整個的監測系統必須是事先就上掉的,而不是我過後再去補,若是爲了系統的健壯性的話我必須是要在整個的系統上線以前把整套的監測系統也作一下部署。
【嘉賓丨童奎鬆】:對,這個監測系統和各類錯誤處理實際上是你程序的一部分。反應式編程四個東西,一個是說你的用戶響應,你的反應無論任何狀況下你的反應時間都要相對在必定範圍以內的。第二,你高負載的時候,照樣能夠撐得住,你不能出錯。第三,當你的系統錯的時候,你也能容錯的,無論是硬件錯仍是你的程序自己有錯,仍是各類網絡錯,你都是要處理一下。這三個是它的要求,由於反應式系統這個要求就是這樣的。因此,爲了達到這三個要求,你剛纔說的監測,剛纔說的自動恢復機制,好比你部署了三個實例,有個實例當掉了,你的系統要負責把重啓新的實例而後補回三個實例。就是你的整個庫存是三個實例,這些東西都是一個自動或者自愈的過程,不能說我今天三個實例當掉一個我經過人去手工啓,這個在如今的高負載的要求下你來不及的。或者剛剛說的,在akka裏面的actor模式,全部的actor會有supervisor,supervisor要負責把不工做的,好比說異常,或者狀態有錯,或者硬件失敗,或者網絡失敗這種失敗的actor所有給殺掉,而後再建立新的actor來替換掉它。
【主持人丨大師兄】:我這邊再說最後一個問題,剛剛的問題都是和監測,測試響應式系統相關。最後一個問題是跟消息自己相關的,在某些場景下可能個人上游系統同時可能會發來許多消息,不止一個的消息,可能你在下游系統當中接收消息的時候會出現這種狀況,其中我有一個消息要等另外一個消息處理完我才能去作。其實有一個消息處理的前後順序的保障,我不知道各位在這個方面有沒有相似的一些實踐呢?若是是我要去保證一個消息處理的順序的狀況下我應該怎麼樣去保證?
【嘉賓丨童奎鬆】:首先我以爲這是一個僞命題,由於你說的消息有先後的,同一個系統發出去之後我會發出去一個消息,這個消息必定會有系統來處理,而後發出另一個消息,另一個消息再觸發下一步,他是一步步來進行的。沒有說我同時收到兩個消息,第二個消息效果等第一個消息處理完以後才能夠處理。更多狀況下是我先收到第一個消息,而後我再處理,發出第二個消息,第二個消息再處理髮出第三個消息。
【嘉賓丨徐鵬程】:存不存在我這個系統發出去兩個消息,可是我接收到這兩個消息的下游消息以後再作下一步,其實至關於一個定型的狀態。可是我又但願他能夠作到,這兩個都作完以後才能作到下一步。
【嘉賓丨童奎鬆】:這個是能夠的,這個其實就是說你在你的系統自己你的狀態,好比咱們把剛剛的例子從新變一下。當個人訂單成功以後我把客戶付款,預留庫存這兩個是同時發生的,這時候庫存系統和付款系統,兩個系統會同時監聽訂單成功這個消息,分別作他的工做,分別發出保留成功和付款成功兩個事件,而後在你的訂單系統裏面你會分別監聽這兩個事件,你不須要關心這兩個事件誰先來誰後來,你只要關心當這兩個事件都來了以後我再進行下一步的處理。
【嘉賓丨徐鵬程】:其實就是說我在接收這兩個消息的時候我去檢查一下另外的有沒有成功?
【嘉賓丨童奎鬆】:你不須要檢查你的另外消息成不成功,由於你不須要關心另外消息成不成功,你只關心你的系統的狀態。由於當我接觸到個人庫存保留成功的狀態的時候我可能會在個人訂單更改一個狀態的時候訂單成功了,在我付款成功以後我可能會生成一個付款成功的交易號,而後填在訂單裏面,無論誰先來後來,只要我後來的檢查到個人訂單裏面有這個狀態了,我就能夠往下一步走,而不是我檢查到那個消息來沒來。
【主持人丨大師兄】:可是以前由於在作最後一步處理的時候其實仍是等消息,而不是去看訂單的狀態,由於訂單的狀態變化自己應該是會發一個消息吧。
【嘉賓丨童奎鬆】:沒有,訂單狀態本身不發消息。
【主持人丨大師兄】:我今天必定要看兩個狀態的變化嗎?
【嘉賓丨徐鵬程】:它至關因而在收到消息的時候觸發那個狀態檢查嗎?
【嘉賓丨童奎鬆】:對,它收到消息的時候你能夠說是中間狀態,這個取決於你的系統怎麼寫,你的程序怎麼寫。你收到兩個消息以後你分別會在你的訂單mark兩個狀態,你後收到的那個你只要檢查前面的是否是收到,或者你的邏輯,你的訂單,你都不須要擔憂個人哪一個消息先到,個人邏輯是說,當個人訂單知足了這個這個以後我走下一步,我無論你這個這個是事件改的,仍是手工改的,仍是經過rest改的,我不在意。
【主持人丨大師兄】:其實遇到一些相似的可能須要有一些dependency的狀況,那我整個把它設計也是歸結爲一個可能某個領域的狀態的變化,可能就是有一些相似於可能我已經付款了,可是庫存沒拿貨這樣一個相似的中間狀態你是須要考慮進去的,幫助你作下一步?
【嘉賓丨童奎鬆】:對的,只要你的業務設計的時候有存在這個可能,你的訂單整個模塊的設計就要考慮到這個狀況。考慮到我付了款,可是沒有庫存,這個時候怎麼辦?是backorder仍是退款?
【嘉賓丨徐焱飛】:我補充一下,在作分佈式消息同步的狀況下可能會有記錄消息的順序問題,作一些分析的時候,由於好比咱們去查某一個特定的時間節點它的先後發生了什麼順序的時候,這個時候若是消息帶有時間順序那對咱們分析這個問題是有幫助的。這種狀況下咱們通常會在消息本身的屬性裏面可能會增長一個好比相似於happenBefore或者happenAfter的關聯關係,把這種關聯關係放在消息自己裏面去。好比一個消息A到一個聚合根的處理過程中產生另一個消息B,那就能夠在消息B當中可能會加一個屬性,就是happenBefore,就是A這個消息是在這個B消息前發生過的。當咱們把全部的消息落地,落到持久層裏面的時候,咱們最終回溯找問題的時候,或者把整個回溯去獲取最新狀態的時候,那這個前後順序是有幫助的。這是基於EventSourcing的方式。
【嘉賓丨童奎鬆】:對,每一個消息必定會有它的timestamp,每一個消息必定有它何時發生的。第二個,加上咱們剛纔說過的UUID,其實你就能夠拎出一條線來。
【主持人丨大師兄】:對,可是UUID應該不會用到一個具體的業務場景中吧,應該也只是你外部的一個監測,從Devops的角度去加這個UUID。
【嘉賓丨童奎鬆】:由於咱們剛剛說的,你的UUID是在你整個的業務流程都存在的。因此你整個業務完成以後你拿到你的事件的列表你就知道這個業務是怎麼走的。你經過你的UUID能找出你全部的事件你就知道你的業務流程怎麼走的,他究竟是走到了訂單建立失敗仍是訂單成功,到底走到了付款成功仍是付款失敗。
【主持人丨大師兄】:對,這個UUID的目的仍是在於背後監測你的系統的狀態,可是自己應該是不會去取這樣一個關係鏈吧。
【嘉賓丨童奎鬆】:會,這個UUID很是重要。
【嘉賓丨徐鵬程】:一般咱們是用來作事件回溯的時候用。
【嘉賓丨童奎鬆】:包括我舉個例子,好比你新版本上線你想測試一下,你怎麼辦?你一種是想作壓力測試,一種是說你生成不少的數據來作壓力測試。第二,你就把老的事件拿過來,UUID排序去作測試。其實就是事件另一個好處,你能夠重放,對於任何的場景你能夠重放。
【主持人丨大師兄】:對,因此就像焱飛剛剛說的,可能就是用來回溯的一個場景,由於回溯可能大部分是作一個問題的查詢或者幫你作測試。可是我在作真正的一個線上的業務邏輯裏面應該不會再去拿這關係鏈去作這樣一個查詢吧。
【嘉賓丨童奎鬆】:對,你真正寫你的程序裏面是不會用UUID來決定作任何identity的,你確定是拿訂單ID或者業務ID來作的。可是這個UUID主要就是說的,一個是你監控,還有一個是你數據分析。好比你過後分析的時候你會把事件聯起來。
【主持人丨大師兄】:咱們今天全部的問題就到這裏了,接下來我想看一下各位聽衆有什麼問題嗎?
【嘉賓丨童奎鬆】:我看到一個問題是狀態機的問題,剛剛咱們說到每一個系統,每一個領域都接收到消息來負責它本身的處理。可是當一些複雜的流程,好比有一些判斷或者複雜的流程的時候每一個系統本身自己已經處理不了的時候你可能會有一個狀態機統一處理,就是我往你哪一個系統去發,負責收到哪一個系統事件,而後告訴哪一個系統要作什麼事情。可是這個狀態機固然有不一樣的作法,一種是說你單獨一個系統去作狀態機來負責整個其實就像你把每一個領域去耦合,第二種作法就是我某一個子系統,我一個子領域做爲狀態機,也就是剛剛說到的訂單系統。可能訂單系統裏面有一個狀態機來決定我全部的事情,針對這張訂單,包括你的預留庫存、付款都是針對這張訂單的,因此我就會把狀態機放在訂單系統裏面。不知道我這個問題解釋得清楚嗎?
【主持人丨大師兄】:因此狀態機制我自己仍是一個,我是否是能夠理解爲它其實不是一個正常的業務流程,而是一個處理平常運營狀況的場景,就是發生了某些狀況下我能夠不從業務的角度,而是從中間去幹預,直接往一箇中間系統去發一個消息,去處理一些可能平常的例外狀況。
【嘉賓丨童奎鬆】:狀態機其實就是業務系統,狀態機有一個好處是你能夠很靈活地修改你的業務流程,多是我經過必定的配置,不須要再修改個人代碼,只是要修改我必定的配置我就能夠從新調整個人業務流程。好比剛剛說的,開始可能我係統設計的時候,我沒有庫存我就不能收款,或者我不能發貨。可能我這時候業務說了,像蘋果來講,我能夠預留三到四周之後發貨,或者我先backorder,這個時候你就須要改流程。一種作法是你改代碼,第二種是我功能都在,我只須要把個人狀態機從新配置一下就能夠了。
【主持人丨大師兄】:剛剛羣裏有人在說仍是提出狀態機的問題他在問狀態機每一個狀態是否都是獨立的?仍是說一個全局的?
【嘉賓丨童奎鬆】:每一個狀態是獨立的,相互不干擾的。可是從個人角度來講,狀態機還有另一個用處,解決衝突。好比你的系統是有狀態的,你要把狀態保存到某個地方,可是你又想分佈式,好比你的數據庫,你的分佈式,這時候你要有兩臺數據庫,以防我某臺數據庫當掉了,我能用另外的數據庫來頂替,這有不一樣的模式,一種是主從模式,就是你是徹底靠主服務。第二種是我主主模式,兩個都是主,你的系統能夠用任何一個數據庫來寫數據,這時候他們之間可能就會產生衝突。
可是衝突有幾種作法,一種是我衝突以後我提醒數據庫按照必定的規則,好比後來的確定是先把前面的覆蓋,這是一種規則。第二種規則,你經過設計狀態機作一種叫conflict free,就不會產生衝突的一個狀態機。當你兩個系統,你的一個訂單在兩個數據庫裏面有不一樣的狀態的時候它會合並進入到另一個狀態。舉例說,當一個訂單在一個地方付款成功,另一個地方是取消的話,那你可能進入第三個狀態,好比用戶取消,這個時候你再根據這個狀態去作相應的業務處理。
【主持人丨大師兄】:因此,你實際上是在說我整個的消息處理的工做的流程中能夠設置多套策略,而後根據不一樣的狀況去運營一些不一樣的策略。
【嘉賓丨童奎鬆】:對,這是狀態機的一個用法。另一個用法我剛剛說了,當你要解決你分佈式裏面的衝突的時候這也是一個作法。
【主持人丨大師兄】:好的,這期就到這裏,謝謝三位嘉賓。
「跨境茶話會」是由移動增加技術服務商「魔窗」聯合國內外衆多技術專家發起的在線技術交流活動,目前已邀請嘉賓來自Google、ebay、Snap、Uber、VISA、Pinterest、BranchMeteics、Splunk、小紅書、華爲等國際知名IT企業在職高級工程師,面向全部互聯網從業技術人員分享交流先進理念和實戰案例,同時爲中美技術朋友提供跨境交友和深度學習的平臺。
「跨境茶話會」結合前沿熱點技術話題,精心策劃每個月一個線上技術主題交流,每期邀請來自國內外互聯網界的三位實戰嘉賓分享一線技術最佳實踐,並針對核心技術問題對話答疑,爲技術從業者拓寬思路、提高技術實力、驅動業務增加。