簡單的訂單業務的基本模型設計用戶、商品(庫存)、訂單、付款,這里只考慮商品和訂單,流程是下訂單 -> 減庫存,這兩步必須同時完成,不能下了訂單不減庫存(超賣),或者減了庫存沒有生成訂單(少賣)。超賣商家庫存不足,消費者下了單買不到東西,體驗不好;少賣商家庫存積壓或者需要反復修改商品信息,反復麻煩,體驗也不好。
在系統初期,承接流量小,很多創業團隊都是單庫的模型(是的,大家都在一起。。。)。這種模型帶來了極大的方便,不用跨庫,更沒有跨節點,能夠方便的利用數據庫提供的事務來實現下單和減庫存的原子操作,還能進行各種聯表和子查詢(運營MM需求再變態,我會SQL能奈我何)。但也正是這些優點,會成為流量上來后對系統進行擴展的絆腳石。聯表、子查詢、事務都是將多張表綁定在了一起,拆庫、拆表就麻煩了。
后期系統流量逐漸升高,單庫的讀寫性能不夠,這時候會考慮將數據庫進行拆庫、分表。比如商品和訂單分為兩個集群,集群內又根據各自業務維度進行分庫和分表,商品可以根據賣家維度來切分,訂單一般根據買家維度切分,并且根據賣家維度做冗余。這個時候出現的問題,還是經典問題——數據一致性,數據拆分后商品和訂單不在一個庫里,怎么保證一致性;買家維度的訂單數據怎么和賣家維度的訂單數據保證一致性。有兩個解決思路:
? ? (1)分布式事務,經典的有基于2PC的實現。優點是封裝得足夠好后使用起來和單庫雖然有區別(主要是復雜查詢語句),但總體來說對業務的改動不會很大。缺點是性能太差,本來引入分布式數據庫主要是為了成倍的提高性能,但因為分布式事務的引入將這個性能的提升大打折扣,很多時候這個性能是難以接受的。
? ? (2)消息中間件,本文主角,消息中間件的一大職能就是負責各個系統間的交流,非常適合這里的商品和訂單系統的同步問題。引入消息中間件后的下單流程是:用戶A下訂單后給消息中間件發送消息,商品系統訂閱訂單消息,并扣除相應的庫存。
這里有幾個注意點:
a、消息的傳遞是需要時間的,下單前查看有庫存,但在并發條件下,實際減庫存時可能庫存不夠,所以必須在庫存扣減成功后才能顯示訂單成功,即下單后標記已下單,但用戶對該狀態不可見,等待商品系統減庫存成功后,再通知訂單系統更新狀態(仍然是消息中間件的運用哦);
b、對消息的可靠性要求很高,發送消息時返回成功就要保證該消息會被投遞,發送失敗需要下單業務自己做回滾;
c、消息的可靠性高表示一定會有消息重復,這里需要商品系統自己做冪等,可以通過消息id來做去重,否則會少賣;
d、下單成功失敗前端用戶都希望盡早得到通知,所以在下單成功后需要設定一個定時消息,在一段時間后如果訂單庫存還沒有扣除成功,這個時候應該通知用戶下單失敗,并且定期回補這部分多扣的庫存。
引入消息中間件,可以很好的解決了分布式數據庫數據同步的問題,避免了分布式事務。并且額外的好處是減少了減庫存時候的并發鎖爭搶,性能杠杠的。