每一年支付寶在雙11和雙12的活動中,都展現了絕佳的技術能力。這個能力不但體如今處理高TPS量的訪問,更體如今幾乎不會出錯,不會出現重複支付的狀況,那這個是怎麼作到的呢?java
誠然,爲了實如今高併發下仍不會出錯的技術目標,支付寶下了不少功夫,好比冪等性的處理,分佈式事務的使用等等,可是我的以爲其中最關鍵的一點就是「一鎖二判三更新」這句看似絕不起眼的口訣。數據庫
何爲「一鎖二判三更新」? 簡單來講就是當任何一個併發請求過來的時候安全
示意圖服務器
話很少說,咱們直接上代碼:架構
//第1步鎖當前支付單
PaymentInfo resultPaymentInfo = commonPayCoreService
.queryPaymentForUpdate(createPaymentInfo.getId());
if (resultPaymentInfo.isFinalStatus()) {
//第2步,判斷當前支付單狀態,若是是終態,則直接返回
//不作任何更新
return resultPaymentInfo;
}
//第3步更新當前支付單狀態到終態,並完成相關業務邏輯(支付成功)
payCoreService.updateRequestResult(payChannelResult);
複製代碼
基於以上方案能夠100%確保在併發狀況下不會出現重複更新問題,按理論來講,就是每次狀態機變動前,都要在併發安全狀況下判斷狀態是否已經發生過變動了。併發
若是第1步或第2步缺失了,會發生什麼問題,咱們來看一下:異步
第1步缺失分佈式
第2步缺失 ![無第2步流程.png] 高併發
只要把這3步做爲咱們的代碼規範,則能夠避免大部分的併發重複操做問題。對於異步併發重複消息的處理亦是如此,加深對狀態機的判斷後還能夠處理消息亂序問題。性能
對於鎖的使用可根據實際狀況選擇[悲觀鎖和樂觀鎖]。 關於悲觀鎖(數據庫行鎖),樂觀鎖(數據庫版本鎖或分佈式鎖)的實現方式和坑咱們之後再詳細說。
可能有人會問無論是悲觀鎖仍是樂觀鎖對系統的併發量都是有影響的,這個怎麼解決?個人觀點是在現代分佈式系統中,若是追求高可用和穩定則必須在方案上優先知足,對於性能能夠經過優化代碼邏輯,優化技術架構,擴展數據庫資源等方式來解決。
在以前螞蟻金服的壓測中,我負責的結算系統內部有10次左右SQL調用以及一次遠程調用(約花費100ms),總流程花費180ms左右。在一臺4核8G的機器上壓測,java服務併發能夠達到150TPS,結果仍是使人滿意的,經過水平服務器擴展徹底沒有問題。
在整個支付寶技術架構中,只有一個場景是沒有用鎖和判斷直接更新的,就是2016年的春節五福紅包,高達上百萬的TPS訪問,爲了保證用戶的順暢體驗,犧牲了狀態判斷的安全性,在過後再作一次對帳(雖然就算出錯也於事無補了 :))