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