自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

大廠都怎么防止重復(fù)下單?

數(shù)據(jù)庫 Oracle
Order服務(wù)調(diào)用Pay服務(wù),剛好網(wǎng)絡(luò)超時(shí),然后Order服務(wù)開始重試機(jī)制,于是Pay服務(wù)對同一支付請求,就接收到了兩次,而且因?yàn)檩喸冐?fù)載均衡算法,落在了不同業(yè)務(wù)節(jié)點(diǎn)!所以一個(gè)分布式系統(tǒng)接口,須保證冪等性。

1.問題背景

最簡單的:DB事務(wù)。如創(chuàng)建訂單時(shí),同時(shí)往訂單表、訂單商品表插數(shù)據(jù),這些Insert須在同一事務(wù)執(zhí)行。

Order服務(wù)調(diào)用Pay服務(wù),剛好網(wǎng)絡(luò)超時(shí),然后Order服務(wù)開始重試機(jī)制,于是Pay服務(wù)對同一支付請求,就接收到了兩次,而且因?yàn)檩喸冐?fù)載均衡算法,落在了不同業(yè)務(wù)節(jié)點(diǎn)!所以一個(gè)分布式系統(tǒng)接口,須保證冪等性。

2.如何避免重復(fù)下單?

前端頁面也可直接防止用戶重復(fù)提交表單,但網(wǎng)絡(luò)錯(cuò)誤會(huì)導(dǎo)致重傳,很多RPC框架、網(wǎng)關(guān)都有自動(dòng)重試機(jī)制,所以重復(fù)請求在前端側(cè)無法完全避免!問題最后還是如何保證服務(wù)接口的冪等性。

2.1 如何判斷請求是重復(fù)的?

  • 插入訂單前,先查一下訂單表,有無重復(fù)訂單? 不優(yōu):難以用SQL條件定義到底什么是“重復(fù)訂單”
  • 訂單的用戶、商品、價(jià)格一樣就是重復(fù)訂單? 萬一這用戶就是連續(xù)下了倆一模一樣訂單呢?

所以保證冪等性要做到:

2.1.1 每個(gè)請求須有唯一標(biāo)識

比如訂單支付請求,得包含訂單id,一個(gè)訂單id最多只能成功支付一次。

2.1.2 每次處理完請求后,須有記錄標(biāo)識該請求已被處理

在MySQL中記錄一個(gè)狀態(tài)字段。如支付之前記錄一條這個(gè)訂單的支付流水。

2.1.3 每次接收請求時(shí),判斷之前是否處理過

若有一個(gè)訂單已支付,就肯定已有一條支付流水。若重復(fù)發(fā)送這個(gè)請求,則此時(shí)先插入支付流水,發(fā)現(xiàn)orderId已存在,唯一約束生效,報(bào)錯(cuò)重復(fù)Key。就不會(huì)再重復(fù)扣款。

在往DB插記錄時(shí),一般不提供主鍵,而由DB在插入時(shí)自動(dòng)生成。這樣重復(fù)的請求就會(huì)導(dǎo)致插入重復(fù)的數(shù)據(jù)。MySQL的主鍵自帶唯一性約束,若在一條INSERT語句提供主鍵,且該主鍵值在表中已存在,則該條INSERT會(huì)執(zhí)行失敗。因此可利用DB的“主鍵唯一約束”,在插數(shù)據(jù)時(shí)帶上主鍵,以此實(shí)現(xiàn)創(chuàng)建訂單接口的冪等性。

給Order服務(wù)添加一個(gè)“orderId生成”的接口,無參,返回值就是一個(gè)【全局唯一】訂單號。在用戶進(jìn)入創(chuàng)建訂單頁面時(shí),前端頁面先調(diào)用該orderId生成接口得到一個(gè)訂單號,在用戶提交訂單時(shí),在創(chuàng)建訂單的請求中攜帶該訂單號。

該訂單號其實(shí)就是訂單表的主鍵,于是,重復(fù)請求中帶的都是同一訂單號。訂單服務(wù)在訂單表中插入數(shù)據(jù)的時(shí)候,執(zhí)行的這些重復(fù)INSERT語句中的主鍵,也都是同一個(gè)訂單號。而DB唯一約束保證,只有一次INSERT執(zhí)行成功。

實(shí)際要結(jié)合業(yè)務(wù),如使用Redis,用orderId作為唯一K。只有成功插入這個(gè)支付流水,才可執(zhí)行扣款。

要求是支付一個(gè)訂單,須插入一條支付流水,order_id建立一個(gè)唯一鍵。你在支付一個(gè)訂單前,先插入一條支付流水,order_id就已經(jīng)傳過去了。就能寫一個(gè)標(biāo)識到Redis中,set order_id payed,當(dāng)重復(fù)請求過來時(shí),先查Redis的order_id對應(yīng)的value,若為payed說明已支付,就別再重復(fù)支付!

然后再重復(fù)支付訂單時(shí),寫嘗試插入一條支付流水,DB會(huì)報(bào)唯一鍵沖突,整個(gè)事務(wù)回滾。保存一個(gè)是否處理過的標(biāo)識也可以,服務(wù)的不同實(shí)例可以一起操作Redis。

圖片

若因重復(fù)訂單導(dǎo)致插入 t_order 失敗,則Order服務(wù)不要把該錯(cuò)誤返給前端頁面。否則,就可能出現(xiàn)用戶點(diǎn)擊創(chuàng)建訂單按鈕后,頁面提示創(chuàng)建訂單失敗,而實(shí)際上訂單創(chuàng)建成功了。

正確做法:這種case,訂單服務(wù)直接返回訂單創(chuàng)建成功。

3.解決ABA

3.1 什么是ABA

如訂單支付后,seller要發(fā)貨,發(fā)貨完成后要填個(gè)快遞單號。假設(shè)seller填個(gè)666,剛填完,發(fā)現(xiàn)填錯(cuò)了,趕緊再修改成888。對訂單服務(wù),這就是2個(gè)更新訂單的請求。系統(tǒng)異常時(shí)666請求到了,單號更成666,接著888請求到了,單號又更新成888,但是666更新成功的響應(yīng)丟了,調(diào)用方?jīng)]收到成功響應(yīng),自動(dòng)重試,再次發(fā)起666請求,單號又被更新成666了,這數(shù)據(jù)顯然就錯(cuò)了!

圖片

3.2 解決方案

訂單主表增加version列。每次查詢訂單時(shí),版本號要隨著訂單數(shù)據(jù)返回給頁面。頁面在更新數(shù)據(jù)的請求中,把這個(gè)版本號作為更新請求的參數(shù),帶回給訂單更新接口。

訂單服務(wù)在更新數(shù)據(jù)的時(shí)候,需要比較訂單的版本號是否和消息中的一致:

  • 不一致拒絕更新數(shù)據(jù)
  • 一致還需再更新數(shù)據(jù)的同時(shí),將version+1。“比較版本號、更新數(shù)據(jù)和版本號+1”的過程須在同一事務(wù)執(zhí)行
UPDATE orders set tracking_number = 666,
version = version + 1
WHERE version = 8;

在這條SQL的WHERE條件中,version值需要頁面在更新的時(shí)候通過請求傳進(jìn)來。

通過該版本號,就能保證,從我打開這條訂單記錄開始,一直到我更新這條訂單記錄成功,期間沒有其他人修改過該訂單數(shù)據(jù)。若有,則DB中的version就會(huì)改變,那我的更新操作就會(huì)執(zhí)行失敗。我就只能重新查詢新版本的訂單數(shù)據(jù),再嘗試更新。

有了這個(gè)版本號,前文的ABA即有兩個(gè)case:

  •  把運(yùn)單號更新為666成功,更新為888的請求帶著舊版本號,就更新失敗,頁面提示用戶更新888失敗
  • 666更新成功后,888帶著新版本號,888更新成功。這時(shí)即使重試的666請求再來,因?yàn)樗蜕弦粭l666請求帶相同版本號,上一條請求更新成功后,這個(gè)版本號已經(jīng)變了,所以重試請求的更新必然失敗

無論哪種情況,DB中的數(shù)據(jù)與頁面上給用戶的反饋都是一致的。這就實(shí)現(xiàn)了冪等更新且避免ABA。

圖片

4.總結(jié)

  • 創(chuàng)建訂單服務(wù),可通過預(yù)生成訂單號,然后利用DB的訂單號唯一約束,避免重復(fù)寫入訂單,實(shí)現(xiàn)創(chuàng)建訂單服務(wù)的冪等性
  • 更新訂單服務(wù),通過一個(gè)版本號機(jī)制,每次更新數(shù)據(jù)前校驗(yàn)版本號,更新數(shù)據(jù)同時(shí)自增版本號,這樣的方式,來解決ABA問題,確保更新訂單服務(wù)的冪等性

兩種冪等的實(shí)現(xiàn)方法,就可以保證,無論請求是不是重復(fù),訂單表中的數(shù)據(jù)都是正確的。

實(shí)現(xiàn)訂單冪等的方法,完全可以套用在其他需要實(shí)現(xiàn)冪等的服務(wù)中,只需要這個(gè)服務(wù)操作的數(shù)據(jù)保存在數(shù)據(jù)庫中,并且有一張帶有主鍵的數(shù)據(jù)表即可。

責(zé)任編輯:武曉燕 來源: JavaEdge
相關(guān)推薦

2024-08-06 08:13:26

2022-11-11 07:34:43

2013-11-13 11:01:14

表單表單重復(fù)提交表單策略

2022-11-15 07:39:48

2022-11-17 07:43:13

2013-11-13 14:39:53

表單提交開發(fā)

2025-04-14 00:00:00

數(shù)據(jù)庫分布式架構(gòu)分布式鎖?

2020-07-17 07:59:55

數(shù)據(jù)

2024-06-06 08:46:37

2024-12-16 00:54:05

2020-12-01 11:13:00

MySQL8

2025-04-01 08:10:00

JavaScripteval()函數(shù)代碼

2024-08-05 09:29:00

前端接口請求

2021-02-02 16:37:25

Redis分布式

2020-11-16 09:15:07

MYSQL

2025-02-26 07:53:21

2010-11-23 16:56:04

mysql表單

2022-05-25 09:55:40

數(shù)據(jù)重復(fù)提交Java

2020-09-18 10:18:08

MySQL數(shù)據(jù)插入數(shù)據(jù)庫

2009-06-05 10:37:52

struts2 國際化表單
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號