面試官扎心一問(wèn):防止重復(fù)請(qǐng)求提交,有什么方案?
本文轉(zhuǎn)載自微信公眾號(hào)「UP技術(shù)控」,作者 conan5566。轉(zhuǎn)載本文請(qǐng)聯(lián)系UP技術(shù)控公眾號(hào)。 conan5566
背景
在平常開(kāi)發(fā)中,我們經(jīng)常會(huì)面對(duì)防止重復(fù)請(qǐng)求的問(wèn)題。當(dāng)服務(wù)端對(duì)于請(qǐng)求的響應(yīng)涉及數(shù)據(jù)的修改,或狀態(tài)的變更時(shí),可能會(huì)造成極大的危害。重復(fù)請(qǐng)求的后果在交易系統(tǒng)、售后維權(quán),以及支付系統(tǒng)中尤其嚴(yán)重。但是很多時(shí)候,都是指望著前端來(lái)限制,比如提交之后,按鈕diseabled之類(lèi)的,其實(shí)這些都是不靠譜的。關(guān)鍵時(shí)候還是需要后端來(lái)校驗(yàn)。
解決方式
1、基于緩存數(shù)據(jù)狀態(tài)的驗(yàn)證
Redis存儲(chǔ)查詢(xún)輕量快速。在request進(jìn)來(lái)的時(shí)候,可以先記錄在緩存中。后續(xù)進(jìn)來(lái)的request每次進(jìn)行驗(yàn)證。整個(gè)流程處理完成,清除緩存。
- if (!CacheExtension.getInstance().AddUnique($"{key}_unique", 1, DateTimeOffset.Now.AddDays(365)))
- {
- LogExtention.getInstance().WriteCustomLogAsync("", "", true, "上批次還未執(zhí)行結(jié)束");
- return ResponseResult.FromError("上批次還未執(zhí)行結(jié)束!");
- }
- if (!string.IsNullOrEmpty(uniqueKey))
- {
- CacheExtension.getInstance().Remove(uniqueKey);
- }
- return ResponseResult.Ok();
2、利用唯一索引機(jī)制的驗(yàn)證
需要原子性操作,想到了數(shù)據(jù)庫(kù)的唯一索引。新建一個(gè)表,每次request進(jìn)來(lái)則往表里面插入數(shù)據(jù), 操作完成后,刪除此條記錄。
3、基于緩存的計(jì)數(shù)器驗(yàn)證
由于數(shù)據(jù)庫(kù)的操作比較消耗性能,了解到redis的計(jì)數(shù)器也是原子性操作。果斷采用計(jì)數(shù)器。既可以提高性能,還不用存儲(chǔ),而且能提升qps的峰值。 每次request進(jìn)來(lái)則新建一個(gè)以orderId為key的計(jì)數(shù)器,然后+1。如果>1(不能獲得鎖): 說(shuō)明有操作在進(jìn)行,刪除。如果=1(獲得鎖): 可以操作。
- redis> SET test 20
- OK
- redis> INCR test
- (integer) 21
- redis> GET test # 數(shù)字值在 Redis 中以字符串的形式保存
- "21"
- //獲取指定的所有計(jì)數(shù)器
- HGETALL counter:user:{userID}
- //獲取指定的指定計(jì)數(shù)器
- HMGET counter:user:{userID} praiseCnt hostCnt
- //指定點(diǎn)贊數(shù)+1
- HINCRBY counter:user:{userID} praiseCnt
總結(jié)
1、c#本身有l(wèi)ock機(jī)制,單體模式可以使用。
2、但是考慮到我們的分布式部署,建議還是用緩存。在大并發(fā)的情況下,程序各種情況的發(fā)生。特別是涉及到金額操作。所以在大并發(fā)要互斥的情況下可以考慮2、3兩種方案。