高并發下如何防止用戶重復下單的解決方案
在我們實際的業務中,用戶在下單頁面進行下單時,由于用戶在短時間內多次點擊下單按鈕或服務自身的重試策略、網絡阻塞等問題,都可能會導致在訂單服務中接收到了同一個用戶的多個同樣的下單請求,進而造成重復下單問題。下面介紹幾種常見的解決用戶重復下單的問題的方案。
1、分析重復下單問題

正常的一次下單流程是:首先會由用戶發起一個下單操作,然后由訂單服務去保存一條訂單信息(此時訂單的狀態是未支付),隨后由用戶去確認訂單,此時支付服務把將訂單狀態改為待支付,然后支付服務跳轉到三方的支付界頁面供用戶去支付,當用戶支付完成之后,由支付平臺進行回調,回調到支付服務后,支付服務再去通知訂單服務去修改訂單的狀態為已支付。
在整個下單過程中,用戶支付的時候,其實是由第三方的支付平臺來進行負責冪等性問題,所以對于我們來講,真正需要我們保證冪等性問題的創建訂單這一步,因為用戶點擊了多次下單按鈕,如果沒有做防重處理,那訂單服務就會生成多條訂單信息(多次下單請求中只有一次請求是有效的)。
對于生成的無效訂單不僅給系統造成了資源的浪費,而且對用戶的體驗也不好,同時對品牌聲譽損害。
2、冪等性的解決方案
2.1、前端攔截方案
當用戶點擊了“購買”按鈕第一次之后,就把這個按鈕置為不可用,防止用戶點擊了多次,如下圖所示:
圖片
這種前端處理的方式只能處理一部分防止重復下單的問題,如果用戶不是通過頁面請求下單,而是直接調用后端接口的方式創建訂單,那么前端攔截就無用了。
2.2、借助Redis的setnx方案
Redis的setnx命令的特性:當它保存一個key和value的時候,如果這個value在Redis中沒有值,那么Redis就會返回true,并且保存成功;如果這個value它有值,那么就會返回false。那么,我們利用Redis的setnx命令的這個特性就可以保證多次操作只能存儲一次值。具體的實現方案如下圖所示:
圖片
當用戶進行下單的時候,我們就可以通過給當前這個用戶分配一個唯一的key來調用setnx命令,key的設計方案如下所示:
圖片
用戶登錄成功之后會有一個用戶的唯一標識Token,我們用Token表示這個用戶;用戶購買某個商品,我們使用商品對應的URL來表示這個商品;最后加一個特定的key(自定義一個常量字符串標識),目的是我們不敢保證當前的這個頁面上是不是只有下單這一個操作需要保證冪等性,有可能還有其他的業務操作,所以加特定的key作為區分。
即使當用戶調用多次下單接口的時候,也只會下單成功一次,那么多余 的無效請求都會下單失敗。當然setnx還需要設置一個過期時間,(如設置成5秒,因為正常的用戶在短時間之內基本上是不可能有這么快的速度能夠重復的去購買這個商品),此時在過期時間內的重復請求我們也不處理,這樣就可以防止重復下單。
2.3、借助Redis的防重Token方案
在用戶打開訂單確認的界面時,除了返回訂單的業務數據外,服務端還會生成一個防重token(簡單一點的防重token就使用UUID),同時設置防重token的過期時間并存儲到Redis中,如下圖所示:
圖片
服務端需要將這個防重token和業務數據一起返回給客戶端,當用戶點擊創建訂單時,下單請求攜帶防重token一起提交服務端,服務端會通過這個防重token來查詢Redis當前的防重Token是否存在:
(1)如果防重Token存在,說明這個請求是有效的,此時我們可以刪除Redis中的防重token(這里需要使用分布式鎖來保證同一個用戶在同一個商品中的同時只有一個請求執行),然后接下來就允許用戶的這個請求創建訂單。
(2)如果防重token已經不存在,那么就認為這個請求是重復的請求,直接不處理(或者打印日志記錄)。
總結:
(1)在實際的應用場景中,前端攔截結合后端的后端的兩種方案中的任意一種,前后端相互配合解決重復下單問題。
(2)建議增加數據庫的兜底方案,即就是通過數據庫自身的唯一約束的解決方案,整理出業務的唯一標識(如訂單id)作為唯一鍵。































