文章很長,且持續更新,建議收藏起來,慢慢讀!瘋狂創客圈總目錄 博客園版 爲您奉上珍貴的學習資源 :
免費贈送 :《尼恩Java面試寶典》 持續更新+ 史上最全 + 面試必備 2000頁+ 面試必備 + 大廠必備 +漲薪必備
免費贈送 :《尼恩技術聖經+高併發系列PDF》 ,幫你 實現技術自由,完成職業升級, 薪酬猛漲!加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷1)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷2)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷3)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取
得物面試:10wqps高併發,如何防止重複下單?
尼恩特別說明: 尼恩的文章,都會在 《技術自由圈》 公號 發佈, 並且維護最新版本。 如果發現圖片 不可見, 請去 《技術自由圈》 公號 查找
此文的公衆號版本 炸裂:MySQL死鎖是什麼,如何解決?
尼恩說在前面
在40歲老架構師 尼恩的讀者交流羣(50+)中,最近有小夥伴拿到了一線互聯網企業如得物、阿里、滴滴、極兔、有贊、希音、百度、網易、美團的面試資格,遇到很多很重要的面試題:
10wqps高併發,如何防止重複提交/支付訂單?
10wqps高併發,如何防止重複下單?
10wqps高併發,如何防止重複支付?
10wqps高併發,如何解決重複操作問題?
最近有小夥伴在面試得物,又遇到了這個的面試題。小夥伴支支吾吾的說了幾句,面試官不滿意,面試掛了。
所以,尼恩給大家做一下系統化、體系化的梳理,使得大家內力猛增,可以充分展示一下大家雄厚的 “技術肌肉”,讓面試官愛到 “不能自已、口水直流”,然後實現”offer直提”。
當然,這道面試題,以及參考答案,也會收入咱們的 《尼恩Java面試寶典PDF》V171版本,供後面的小夥伴參考,提升大家的 3高 架構、設計、開發水平。
最新《尼恩 架構筆記》《尼恩高併發三部曲》《尼恩Java面試寶典》的PDF,請關注本公衆號【技術自由圈】獲取,回覆:領電子書
基礎知識:電商訂單支付核心流程
首先,來看看 訂單支付的業務流程和交互流程。
圖解:訂單支付的業務流程和交互流程
結合下圖來看看 訂單支付的業務流程和交互流程。
訂單支付流程, 分爲 大致 的 6個步驟 :
1.下單/結算:
下單作爲支付的入口,但並非起點,
支付相關的金額等信息全部來至結算,此時訂單處於 未支付 狀態。
2.申請支付:
用戶開始申請支付,客戶端調用支付服務,
此時在支付系統內產生一筆訂單支付流水,這筆支付流水處於 未支付 狀態。
3.發起支付:
支付服務調用 第三方支付平臺,
通常, 第三方支付平臺 是 錢包類的支付方式,
在發起支付這一步驟,支付平臺會響應一些支付的鏈接,客戶端會對鏈接進行相應的處理。
4.錢包支付:
用戶進行支付,
用戶 APP端直接拉去錢包進行支付。
5.支付回調:
用戶完成支付之後,三方支付平臺會回調 商戶的支付服務 接口,通知支付結果。
6.更新訂單狀態:
支付服務 確認訂單支付完成後,會向 訂單服務同步 支付的結果。
訂單服務變更服務的狀態:未支付
變更爲 待發貨
。
客戶端通過輪詢、長輪詢,或者服務端主動推送的方式,在界面上變更訂單狀態。
圖解:支付狀態的變化
如下圖,從支付流水角度來分析一下支付狀態的變化:
1.從未支付,到有支付結果的終態,中間還有一箇中間狀態:支付中
2.戶通過打開錢包--》完成支付--》支付回調,這段時間的支付流水就處於:支付中
重複下單的定義、危害、應對策略
什麼是重複下單
現在問題來了, 什麼是重複下單?
用戶在下單頁面進行下單時,由於用戶點擊下單按鈕 多次 、或者 重試策略 導致在訂單服務中接收到了 兩次同樣 的下單請求。
重複下單帶來的危害
重複下單場景,第N次的下單會對數據進行打亂,導致系統整體數據異常
- 庫存數據異常
- 金額數據異常
- 優惠券數據異常
- 等等
重複下單場景,第N次的下單需要等第一次下單操作完成
重複下單帶來的危害, 總結起來,有以下幾點:
1.系統資源佔用與性能下降
-
重複下單會佔用系統資源,包括服務器、數據庫等,特別是在下單高峯期,可能導致系統性能下降,響應速度變慢。
-
重複請求可能引發系統擁堵,影響其他正常用戶的購物體驗。
2.訂單處理複雜性增加
- 商家在處理訂單時,需要花費額外的時間和精力去識別、合併或取消重複訂單,增加了訂單處理的複雜性。
- 重複訂單可能導致庫存數量出現錯誤,進而影響後續訂單的履行。
3.財務結算與對賬難度增大
- 重複下單可能導致財務結算時出現混亂,需要花費更多時間和精力去核對和調整賬目。
- 對賬過程中需要區分哪些是重複訂單,哪些是有效訂單,增加了對賬的難度。
4.用戶體驗受損
- 消費者在遇到重複下單時,可能會感到困惑和不滿,影響對電商平臺的信任度和忠誠度。
- 重複下單可能導致消費者錯過優惠活動或促銷時機,影響其購物體驗。
5.數據異常與決策誤導
- 重複下單的數據會干擾銷售數據的準確性,可能導致商家在決策時受到誤導。
- 錯誤的銷售數據可能影響商家的庫存規劃、生產計劃等關鍵決策。
6.售後服務與退換貨問題
- 如果消費者對重複下單的商品申請了退換貨,會增加售後服務的處理難度和成本。
- 重複訂單可能導致退換貨政策執行混亂,影響消費者的售後體驗。
7.安全風險與欺詐行爲
- 重複下單有時可能是惡意行爲,如刷單、欺詐等,給電商平臺帶來安全風險。
- 需要重點加強對重複下單的監控和識別,以防範潛在的安全風險。
重複下單問題,主要解決辦法就是做好冪等,因爲在分佈式系統中,我們是沒有辦法保證用戶一定不會快速點擊兩次下單。
Order 服務調用 Pay 服務,剛好網絡超時,然後 Order 服務開始重試機制,於是 Pay 服務對同一支付請求,就接收到了兩次,而且因爲輪詢負載均衡算法,請求落在了不同業務服務節點,所以一個分佈式系統服務,須保證冪等性。
什麼場景下回發生重複下單?
場景1:客戶端bug
用戶短時間內多次點擊下單按鈕,或瀏覽器刷新按鈕導致。
比如下單的按鍵在點按之後,在沒有收到服務器請求之前,按鍵的狀態沒有設爲已禁用狀態,還可以繼續點擊。又或者,在觸摸屏下,用戶手指的點按可能被手機操作系統識別爲多次點擊。
場景2:超時重試
Nginx或Spring Cloud Gateway 網關層、RPC通信重試或業務層重試,進行超時重試導致的。
用戶的設備與服務器之間,可能是不穩定的網路。這樣一個下單請求過去,服務器不一定及時返回結果。
超時最大的問題:從用戶的角度,他無法確定下單的請求是否達到服務器,還是已經到了服務器但是返回結果時數據丟失了。所以用戶無法區分到底這個訂單是否下單成功。
場景3:用戶APP強退/閃退之後重新下單
心急的用戶可能會重啓流程/重啓App/重啓手機。在這種強制的手段下,任何技術手段都會失效。
場景4:黑客或惡意用戶
黑客或惡意用戶使用postman等網絡工具,重複惡意提交訂單。
重複下單問題與冪等性問題
重複下單問題,本質上,就是下單操作的冪等性問題
說到底,“下單防重”的問題是屬於“接口冪等性”的問題範疇。
什麼是冪等性問題?
所謂冪等性,就是一次操作和多次操作同一個資源,所產生的影響均與一次操作的影響相同。
"冪等(idempotent、idempotence)是一個數學與計算機學概念,常見於抽象代數中。
冪等函數,或冪等方法,是指可以使用相同參數重複執行,並能獲得相同結果的函數。
冪等性,用數學語言表達就是:
f(x)=f(f(x))
維基百科的冪等性定義如下:
冪等(idempotent、idempotence)是一個數學與計算機學概念,常見於抽象代數中。
冪等函數,或冪等方法,是指可以使用相同參數重複執行,並能獲得相同結果的函數。
這些函數不會影響系統狀態,也不用擔心重複執行會對系統造成改變。
例如,“setTrue()”函數就是一個冪等函數,無論多次執行,其結果都是一樣的,更復雜的操作冪等保證是利用唯一交易號(流水號)實現.
通俗點說:
一個接口如果冪等,不管被調多少次,只要參數不變,結果也不變。
冪等性是對於寫操作來說的,一個寫操作,一般都需要保證:
-
冪等性
-
可用性
-
ACID事務屬性。
上述內容選自尼恩這篇硬核文章:
如何解決接口冪等問題
接口接口冪等問題,只需記住一句口令:一鎖、二判、三更新。只需嚴格按照這個過程,那麼就可以解決接口冪等問題,總結如下:
1.一鎖:先加鎖,可以加分佈式鎖、悲觀鎖都可以,但是一定是一個互斥鎖。
2.二判:進行冪等性判斷,可以基於狀態機、業務流水錶、數據庫唯一索引等,進行重複操作的判斷。
3.三更新:對數據進行更新,將數據進行持久化。
關於冪等性方案,請參見尼恩這篇硬核文章:
如何解決重複下單問題?
方案一:提交訂單按鈕置灰
防止用戶提交,最常規的做法,就是客戶端點擊下單之後,在收到服務端響應之前,按鈕置灰。
前端頁面直接防止用戶重複提交表單,但網絡錯誤會導致重傳,很多RPC框架、網關都有自動重試機制,所以重複請求在前端側無法完全避免。
當然,這種方案也不是真的沒有價值。
這種方案可以在高併發場景下,從瀏覽器端去攔住一部分請求,減少後端服務器的處理壓力,達到過濾流量的效果。
方案一優點:簡單。基本可以防止重複點擊提交按鈕造成的重複提交問題。
方案一缺點:前進後退操作,或者F5刷新頁面等問題並不能得到解決。
方案二:請求唯一ID+數據庫唯一索引約束
首先來向大家介紹一種最簡單的、成本最低的解決方案。
防重是第一步,需要識別是否重複請求,
所以,需要客戶端在請求下單接口的時候,需要生成一個唯一的請求號:requestId
,服務端拿這個請求號,判斷是否重複請求。
核心流程圖:
實現的邏輯,流程如下:
-
當用戶進入訂單提交界面的時候,調用後端獲取請求唯一ID,並將唯一ID值埋點在頁面裏面。
-
當用戶點擊提交按鈕時,後端檢查這個唯一ID是否用過,如果沒有用過,繼續後續邏輯;如果用過,就提示重複提交。
-
最關鍵的一步操作,就是把這個唯一ID 存入業務表中,同時設置這個字段爲唯一索引類型,從數據庫層面做防止重複提交。
對於下單流量不算高的系統,可以採用這種 請求唯一ID + 數據表增加唯一索引約束`的方式,來防止接口重複提交!
但是這個併發量太低,10wqps高併發, 這個根本沒法滿足。
方案三:reids分佈式鎖+請求唯一ID
在上一個方案中,我們詳細的介紹了對於下單流量不算高的系統,可以通過 請求唯一ID+數據表增加唯一索引約束`這種方案來實現防止接口重複提交!
隨着業務的快速增長,每一秒的下單請求次數,可能從幾十上升到幾百甚至幾萬。
面對這種下單流量越來越高的場景,此時數據庫的訪問壓力會急劇上升,數據庫會成爲整個下單流程的瓶頸。
對於這樣的場景,我們可以選擇引入緩存中間件來緩解數據庫高併發場景下的壓力,
下面,我們以引入redis
緩存中間件,向大家介紹具體的解決方案。
流程如下:
-
當用戶進入訂單提交界面的時候,調用後端獲取請求唯一 ID,同時後端將請求唯一ID存儲到
redis
中再返回給前端,前端將唯一 ID 值埋點在頁面裏面。 -
當用戶點擊提交按鈕時,後端檢查這個請求唯一 ID 是否存在,如果不存在,提示錯誤信息;如果存在,繼續後續檢查流程。
-
使用
redis
的分佈式鎖服務,對請求 ID 在限定的時間內進行加鎖,如果加鎖成功,繼續後續流程;如果加鎖失敗,提示說明:服務正在處理,請勿重複提交。 -
最後一步,如果加鎖成功後,需要將鎖手動釋放掉,以免再次請求時,提示同樣的信息;同時如果任務執行成功,需要將
redis
中的請求唯一 ID 清理掉。
至於數據庫是否需要增加字段唯一索引,理論上可以不用加,如果加了更保險。
這個通過擴展,可以滿足 10wqps高併發要求。
具體的擴展方案, 即將在 《尼恩Java面試寶典》 配套視頻 發佈。
方案四:reids分佈式鎖+token
在上一個方案中,每次提交訂單的時候,都需要調用服務端獲取請求唯一ID:requestId,然後才能提交,這裏面存在以下問題:
下單鏈路中,多了的一次請求, 這一次請求專門用於請求 request id。這次請求是否可以減少呢?
當然是可以的,比如, 可以用戶的請求的特徵數據,根據特定規則生成token,來替代 那個專用的 request id。
而不用專門去來減少一次客戶端與服務端之間的交互次數,提高下單流程效率。
特定規則生成token, 比如說,可以組合一些核心參數,去生成token, 核心參數包括:
應用名+接口名+方法名+請求參數簽名(請求header、body參數,取SHA1值)
組合一些核心參數,去生成token ,大致 流程如下:
-
用戶點擊提交按鈕,服務端接受到請求後,通過規則計算出本次請求唯一ID值
-
使用
redis
的分佈式鎖服務,對請求 ID 在限定的時間內嘗試進行加鎖,如果加鎖成功,繼續後續流程;如果加鎖失敗,說明服務正在處理,請勿重複提交。 -
最後一步,如果加鎖成功後,需要將鎖手動釋放掉,以免再次請求時,提示同樣的信息。
方案四和方式三的最大不同,在於 唯一請求 ID 的生成 環節,
方案四 放在服務端通過組合來實現 唯一請求 ID 的生成 ,在保證防止接口重複提交的效果同時,也可以顯著的降低接口測試複雜度!
方案四的性能,比方案三更高。
方案五:技術+產品+運營支持
如果經過上述方案處理,還是會有用戶誤操作,直到收到兩份商品才發現下重了。
在實際設計中,無論多麼好的技術,也不可能100%的攔截所有的可能性,必須依靠技術+產品設計+運營支持
的綜合手段才能解決這類問題。
此時就得依靠運營/客服的支持了。
所以即便京東這一類電商等也是配合運營手段進行處理。
實操:reids分佈式鎖+token 解決重複下單的問題
只講理論,是耍流氓
40歲老架構師一直強調, 實操,實操,實操纔是王道
比如咱們社羣的 k8s 實操:
比如咱們社羣的 AT+TCC模式混合事務實操 ):
此實操即將配合 《尼恩Java面試寶典視頻》發佈
接下來,咱們開始 reids分佈式鎖+token 解決重複下單的問題的實操
此實操即將配合 《尼恩Java面試寶典視頻》發佈
實操step1:使用AOP進行 BizToken 的無入侵生成
定義一個註解 BizToken
在業務層或者 控制層,進行BizToken 的使用
實操step2:編寫服務驗證邏輯,通過 aop 代理方式實現
此 aop 切面的 具體的實操演示,請參見 《尼恩Java面試寶典》 視頻
實操step3:使用redission分佈式鎖保證冪等
在BizToken校驗邏輯用到了redis
分佈式鎖保證冪等,
redission分佈式鎖 具體實現邏輯如下:
通過封裝 redission的分佈式鎖來實現 鎖的功能:
具體的實現,委託到 redission的分佈式鎖來實現
具體的實操演示,請參見 《尼恩Java面試寶典》 視頻
10wqps高併發,防止重複下單總結
防止重複下單,本質上就是先做重複判斷,然後服務端做好冪等性控制,結合實際業務場景選擇相應的方案。
實現冪等性需要先理解自身業務需求,根據業務邏輯來實現這樣才合理,處理好其中的每一個結點細節,完善整體的業務流程設計,才能更好的保證系統正常運行。
說在最後:有問題找老架構取經
10wqps高併發,如何防止重複下單?
如果大家能對答如流,如數家珍,基本上 面試官會被你 震驚到、吸引到。
最終,讓面試官愛到 “不能自已、口水直流”。offer, 也就來了。
在面試之前,建議大家系統化的刷一波 5000頁《尼恩Java面試寶典PDF》,裏邊有大量的大廠真題、面試難題、架構難題。很多小夥伴刷完後, 吊打面試官, 大廠橫着走。
在刷題過程中,如果有啥問題,大家可以來 找 40歲老架構師尼恩交流。
另外,如果沒有面試機會,可以找尼恩來改簡歷、做幫扶。
遇到職業難題,找老架構取經, 可以省去太多的折騰,省去太多的彎路。
尼恩指導了大量的小夥伴上岸,前段時間,尼恩指導一個40歲+被裁小夥伴,拿到了一個年薪100W的offer。
狠狠卷,實現 “offer自由” 很容易的, 前段時間一個武漢的跟着尼恩捲了2年的小夥伴, 在極度嚴寒/痛苦被裁的環境下, offer拿到手軟, 實現真正的 “offer自由” 。
技術自由的實現路徑:
實現你的 架構自由:
《阿里二面:千萬級、億級數據,如何性能優化? 教科書級 答案來了》
《峯值21WQps、億級DAU,小遊戲《羊了個羊》是怎麼架構的?》
… 更多架構文章,正在添加中
實現你的 響應式 自由:
這是老版本 《Flux、Mono、Reactor 實戰(史上最全)》
實現你的 spring cloud 自由:
《Spring cloud Alibaba 學習聖經》 PDF
《分庫分表 Sharding-JDBC 底層原理、核心實戰(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關係(史上最全)》
實現你的 linux 自由:
實現你的 網絡 自由:
《網絡三張表:ARP表, MAC表, 路由表,實現你的網絡自由!!》
實現你的 分佈式鎖 自由:
實現你的 王者組件 自由:
《隊列之王: Disruptor 原理、架構、源碼 一文穿透》
《緩存之王:Caffeine 源碼、架構、原理(史上最全,10W字 超級長文)》
《Java Agent 探針、字節碼增強 ByteBuddy(史上最全)》