接下來,羅晟主要與鐵路集團的幾名技術專家討論。
“今天上午我在得知消息順帶簡單研究了一下‘12306’的服務端架構。”羅晟面向眾人有條不紊的說道:“廣大訪問者都在噴,但是我知道12306服務一上線試運行,就承受著這個世界上任何秒殺系統都無法超越的QPS,上百萬的并發再正常不過了。”
在場的幾名鐵路集團的技術骨干人員內心稀里嘩啦的感動,理解萬歲啊。
不懂技術的領導最難溝通,覺得沒有盡力。
羅晟的話還是很有分量的。
過了片刻,羅晟補充道:“高并發的系統架構要采用分布式集群部署,服務上層有著層層負載均衡,并提供各種容災手段,所謂的容災手段就是雙火機房、節點容錯、服務器災備等。保證系統的高可用,流量也會根據不同的負載能力和配置策略均衡到不同的服務器上。”
“即便如此,集群中的單機所能承受的QPS也是非常高的,那么如何將單機性能優化到極致呢?要解決這個問題要先弄明白一件事:通常訂票系統要處理生成訂單、減扣庫存、用戶支付這三個基本的階段,系統要做的事情就是保證火車票訂到不超賣、不少賣、每張售賣的車票都必須支付才有效,還要保證系統承受極高的并發。”
幾名鐵路集團的技術專家連連點頭表示認同,技術痛點就在這里。
羅晟繼續說道:“下單減庫存。當用戶并發請求到達服務端時,首先創建訂單,然后扣除庫存,等待用戶支付。這種順序是我們一般人首先會想到的解決方案,這種情況下也能保證訂單不會超賣,但也會產生一些問題,第一就是在極限并發的情況下,任何一個內存操作的細節都至關影響性能,尤其是像創建訂單這種邏輯,基本都需要存儲到磁盤數據庫的,對數據庫的壓力是可想而知的,12306應該是用的甲骨文數據庫,別花這個冤枉錢了,放到我的星云上。”
“第二是如果用戶存在惡意下單的情況,只下單不支付這樣庫存就會變少,會少賣很多訂單,雖然服務端可以限制IP和用戶的購買訂單數量,但這也真心不算是一個好辦法。”
“然后就是支付減庫存了。如果等待用戶支付了訂單再減庫存,第一感覺就是不會少賣。但這是并發架構的大忌,因為在極限并發的情況下,用戶可能會創建很多訂單,當庫存減為零的時候很多用戶會發現搶到的訂單支付不了,這也就是所謂的‘超賣’,也不能避免并發操作數據庫磁盤IO。”
“最后是預扣庫存。從上面兩種方案的考慮,可以得出結論:只要創建訂單,就要頻繁操作數據庫IO。那么有沒有一種不需要直接操作數據庫IO的解決方案呢?答案是有,就是預扣庫存,先扣除了庫存,保證不超賣,然后異步生成用戶訂單,這樣響應給用戶的速度會快很多。”
“那么怎么保證不少賣呢?用戶拿到了訂單,不支付怎么辦?訂單都應該有效期,比如說用戶五分鐘內不支付,訂單就失效,就會加入新的庫存。訂單的生成是異步的,應該放到即時消費隊列中處理……”
劉副總聽的云里霧里的,但是他發現羅晟和他帶來的幾個技術專家交流的愈發火熱,似乎也得出了一個信息。
找對人了!
這時,羅晟打開了房間里的墻面上的大屏幕,他也拿來了一臺筆記本工作電腦打開,示意眾人看向主投屏,自己一邊操作電腦一邊說道:
“Go語言原生為并發設計,我就采用Go語言給各位演示一下單機搶票的具體流程以及我優化后的解決方案。”
“Go包中的init函數先于main函數執行,也在這個階段主要做一些準備性質的工作。系統需要做的準備工作有:初始化本地庫存、初始化遠程redis存儲統一庫存的hash鍵值、初始化redis鏈接池。”