我們的訂單事件服務是事件驅動設計在訂單領域的一個實踐。訂單系統(tǒng)將訂單表 Binlog 作為事件源,通過基于 Canal 的 Binlog 服務轉換為 MQ 消息,然后我們的訂單事件服務將消息轉換為訂單業(yè)務事件,驅動后續(xù)業(yè)務流程。
接下來給大家介紹一下此設計方案的優(yōu)點和在實踐過程中需要注意的細節(jié)。
一、優(yōu)點
這么設計好處有以下幾點:
- 簡化事件設計,集中事件生成規(guī)則
- 簡化數(shù)據(jù)一致性設計
- 提高前臺業(yè)務服務穩(wěn)定性
接下來分別介紹一下。
簡化事件設計,集中事件生成規(guī)則
在之前的設計中,訂單事件是由前臺系統(tǒng)直接在訂單數(shù)據(jù)更新成功后發(fā)出。通過業(yè)務代碼判斷訂單事件類型,不同的接口,不同的系統(tǒng)都會耦合不同的事件生成規(guī)則。這導致底層的訂單事件生成規(guī)則不統(tǒng)一,存在重復的問題。這些問題在引入訂單事件服務,通過 MySQL Binlog 消息生成訂單事件后得以解決。訂單事件生成規(guī)則集中到了訂單事件服務中,可維護性和規(guī)則清晰度都有明顯提升。
簡化數(shù)據(jù)一致性設計
如果讓業(yè)務在更新數(shù)據(jù)后直接發(fā)消息,需要考慮如何保證數(shù)據(jù)庫和 MQ 的數(shù)據(jù)一致。如果數(shù)據(jù)庫事務提交成功,MQ 發(fā)送失敗,怎么辦?通常的方法是重試,實現(xiàn)最大努力通知。但重試也有細節(jié)問題,同步重試容易導致服務線程掛起時間過程,異步重試又需要考慮如何保持重試任務。當然這些都有辦法解決,但通常代價都是嚴重增加了業(yè)務系統(tǒng)設計的復雜度。
因此,通過使用使用 Binlog 驅動的事件設計,可以降低前臺業(yè)務系統(tǒng)在這些方面的設計難度,將技術細節(jié)和優(yōu)化工作下沉至底層。
提高前臺業(yè)務系統(tǒng)穩(wěn)定性
前臺業(yè)務系統(tǒng)直接依賴 MQ 在當時使用 ActiveMQ 時更為明顯,ActiveMQ 服務不穩(wěn)定,抗消息積壓能力差。前臺業(yè)務系統(tǒng)直接作為消息 Producer 依賴 MQ Broker 勢必會降低系統(tǒng)整體的可用性和穩(wěn)定性。
二、需要注意的細節(jié)
但使用訂單表的 Binlog 作為事件源的設計方法有幾個技術細節(jié)需要注意。這些細節(jié)不注意就很容易引起線上問題(別問是怎么知道的)。
第一,數(shù)據(jù)庫運維所帶來的影響
第二,注意 Binlog 寫入和事務生效時機
第三,注意鏈路上各服務的穩(wěn)定性和可用性
接下來詳細介紹這三點
數(shù)據(jù)庫運維帶來的影響
直接使用業(yè)務數(shù)據(jù)表作為事件表雖然簡化了業(yè)務系統(tǒng)開發(fā)工作,但是增加了運維工作潛在影響的可能。當數(shù)據(jù)庫需要增加字段、增加索引時。傳統(tǒng)的 MySQL alter 語句顯然不能使用,因為這個會導致鎖表,影響線上服務。常用的方法是使用如 GitHub 開源的 gh-ost 工具、Percona 開源的工具,將整個數(shù)據(jù)表拆分為小塊進行運維工作。但這樣會導致每條數(shù)據(jù)都會產(chǎn)生一條 Binlog 數(shù)據(jù),最終導致巨量 Binlog。具體來說,在做分庫分表之后,最近一段時間內(nèi),每個表依然有百萬級的熱數(shù)據(jù),如果短時間內(nèi)完成運維工作,將導致大量 Binlog,從而加重 Binlog 消息系統(tǒng)負載,使得真正的業(yè)務 Binlog 消息無法及時處理,導致業(yè)務延遲。因為會員訂單業(yè)務要求用戶支付成功后秒級完成權益開通,所以這樣的延遲極易導致線上保障。
解決的方法是數(shù)據(jù)庫運維平臺增加了數(shù)據(jù)表分塊運維的間隔,降低了速度,減緩 Binlog 產(chǎn)生速度。同時,Binlog 消息平臺做出相應優(yōu)化,加快處理速度。目前新版的 Binlog 消息平臺速度比過去提高了兩倍。
但降低運維速度又產(chǎn)生新的問題,那就是加索引加字段可能需要一天的時間才能完成,有時對業(yè)務還是不容忽視。
解決這個問題有兩種方法,控制業(yè)務表數(shù)據(jù)量和使用專門的事件表。此外在大廠自研的數(shù)據(jù)庫也有一些解決方法,不過細節(jié)我暫時不清楚,了解的同學可以給我留言。
Binlog 寫入和事務生效的時機
這個問題之前專門寫文章介紹過,這里在重復一下。訂單事件發(fā)出后,后續(xù)業(yè)務系統(tǒng)通常會再次檢查訂單狀態(tài),保證業(yè)務邏輯的嚴謹。但在事件中,我們發(fā)現(xiàn)過訂單事件發(fā)出后,當后續(xù)系統(tǒng)檢查訂單狀態(tài)時,發(fā)現(xiàn)主庫中訂單狀態(tài)還是之前的,導致業(yè)務處理被跳過。
這個問題的原因是 MySQL 寫 Binlog 和 InnoDB 引起更新數(shù)據(jù)頁是整個事務提交動作中的兩步,有前后關系。所以實際當 Binlog 發(fā)出后,數(shù)據(jù)頁的內(nèi)容有可能尚未變更,導致后續(xù)系統(tǒng)查到的數(shù)據(jù)依舊是舊的。
這可以通過在業(yè)務狀態(tài)檢查上適當增加重試解決。
注意鏈路上各服務的穩(wěn)定性和可用性
在使用基于業(yè)務表 Binlog 的事件驅動設計后,整個業(yè)務鏈路增加了一個 Binlog 平臺和一個 MQ,整個鏈路更加復雜。理論上依賴越多,可靠性越差,因為整體可靠性是每個分部可靠性的乘積。
為了解決這個問題,我們又為關鍵的訂單業(yè)務處理增加了不依賴訂單事件的備份通路,保證當訂單事件機制出現(xiàn)技術問題時,核心流程依舊可使用備份通路完成。為保證備份通路和正常通路的可用性和業(yè)務邏輯與基于訂單事件的處理保持一致,兩者都是基于相同的系統(tǒng),只是接受數(shù)據(jù)的方式存在不同。
三、如果重新設計
如果重新設計訂單事件驅動設計,我會考慮將訂單創(chuàng)建、更新操作從業(yè)務系統(tǒng)下沉至訂單平臺中的訂單核心服務。在其中引入訂單事件表,使用 JSON 存儲數(shù)據(jù),引入基于訂單號的局部消息順序設計,加強數(shù)據(jù)一致性。具體設計這里就不詳細介紹了,歡迎大家交流討論。