国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - 編程技術 - 我們一起聊聊冪等設計

我們一起聊聊冪等設計

2022-01-04 21:57撿田螺的小男孩 編程技術

我們開發一個轉賬功能,假設我們調用下游接口超時了。一般情況下,超時可能是網絡傳輸丟包的問題,也可能是請求時沒送到,還有可能是請求到了,返回結果卻丟了。這時候我們是否可以重試呢?如果重試的話,是否會多轉了一

我們一起聊聊冪等設計

前言

大家好,我是程序員田螺。今天我們一起來聊聊冪等設計。

  • 什么是冪等
  • 為什么需要冪等
  • 接口超時,如何處理呢?
  • 如何設計冪等?
  • 實現冪等的8種方案
  • HTTP的冪等

1. 什么是冪等?

冪等是一個數學與計算機科學概念。

  • 在數學中,冪等用函數表達式就是:f(x) = f(f(x))。比如求絕對值的函數,就是冪等的,abs(x) = abs(abs(x))。
  • 計算機科學中,冪等表示一次和多次請求某一個資源應該具有同樣的副作用,或者說,多次請求所產生的影響與一次請求執行的影響效果相同。

2. 為什么需要冪等

舉個例子:

我們開發一個轉賬功能,假設我們調用下游接口超時了。一般情況下,超時可能是網絡傳輸丟包的問題,也可能是請求時沒送到,還有可能是請求到了,返回結果卻丟了。這時候我們是否可以重試呢?如果重試的話,是否會多轉了一筆錢呢?

我們一起聊聊冪等設計

轉賬超時

當前互聯網的系統幾乎都是解耦隔離后,會存在各個不同系統的相互遠程調用。調用遠程服務會有三個狀態:成功,失敗,或者超時。前兩者都是明確的狀態,而超時則是未知狀態。我們轉賬超時的時候,如果下游轉賬系統做好冪等控制,我們發起重試,那即可以保證轉賬正常進行,又可以保證不會多轉一筆。

其實除了轉賬這個例子,日常開發中,還有很多很多例子需要考慮冪等。比如:

  • MQ(消息中間件)消費者讀取消息時,有可能會讀取到重復消息。(重復消費)
  • 比如提交form表單時,如果快速點擊提交按鈕,可能產生了兩條一樣的數據(前端重復提交)

3. 接口超時了,到底如何處理?

如果我們調用下游接口超時了,我們應該怎么處理呢?

有兩種方案處理:

  • 方案一:就是下游系統提供一個對應的查詢接口。如果接口超時了,先查下對應的記錄,如果查到是成功,就走成功流程,如果是失敗,就按失敗處理。

拿我們的轉賬例子來說,轉賬系統提供一個查詢轉賬記錄的接口,如果渠道系統調用轉賬系統超時時,渠道系統先去查詢一下這筆記錄,看下這筆轉賬記錄成功還是失敗,如果成功就走成功流程,失敗再重試發起轉賬。

我們一起聊聊冪等設計

方案二:下游接口支持冪等,上游系統如果調用超時,發起重試即可。

我們一起聊聊冪等設計

兩種方案都是挺不錯的,但是如果是MQ重復消費的場景,方案一處理并不是很妥,所以,我們還是要求下游系統對外接口支持冪等。

4. 如何設計冪等

既然這么多場景需要考慮冪等,那我們如何設計冪等呢?

冪等意味著一條請求的唯一性。不管是你哪個方案去設計冪等,都需要一個全局唯一的ID,去標記這個請求是獨一無二的。

  • 如果你是利用唯一索引控制冪等,那唯一索引是唯一的
  • 如果你是利用數據庫主鍵控制冪等,那主鍵是唯一的
  • 如果你是悲觀鎖的方式,底層標記還是全局唯一的ID

4.1 全局的唯一性ID

全局唯一性ID,我們怎么去生成呢?你可以回想下,數據庫主鍵Id怎么生成的呢?

是的,我們可以使用UUID,但是UUID的缺點比較明顯,它字符串占用的空間比較大,生成的ID過于隨機,可讀性差,而且沒有遞增。

我們還可以使用雪花算法(Snowflake) 生成唯一性ID。

雪花算法是一種生成分布式全局唯一ID的算法,生成的ID稱為Snowflake IDs。這種算法由Twitter創建,并用于推文的ID。

一個Snowflake ID有64位。

  • 第1位:Java中long的最高位是符號位代表正負,正數是0,負數是1,一般生成ID都為正數,所以默認為0。
  • 接下來前41位是時間戳,表示了自選定的時期以來的毫秒數。
  • 接下來的10位代表計算機ID,防止沖突。
  • 其余12位代表每臺機器上生成ID的序列號,這允許在同一毫秒內創建多個Snowflake ID。

我們一起聊聊冪等設計

雪花算法

當然,全局唯一性的ID,還可以使用百度的Uidgenerator,或者美團的Leaf。

4.2 冪等設計的基本流程

冪等處理的過程,說到底其實就是過濾一下已經收到的請求,當然,請求一定要有一個全局唯一的ID標記哈。然后,怎么判斷請求是否之前收到過呢?把請求儲存起來,收到請求時,先查下存儲記錄,記錄存在就返回上次的結果,不存在就處理請求。

一般的冪等處理就是這樣啦,如下:

我們一起聊聊冪等設計

5. 實現冪等的8種方案

冪等設計的基本流程都是類似的,我們簡簡單單來過一下冪等實現的8中方案哈

5.1 select+insert+主鍵/唯一索引沖突

日常開發中,為了實現交易接口冪等,我是這樣實現的:

交易請求過來,我會先根據請求的唯一流水號 bizSeq字段,先select一下數據庫的流水表

  • 如果數據已經存在,就攔截是重復請求,直接返回成功;
  • 如果數據不存在,就執行insert插入,如果insert成功,則直接返回成功,如果insert產生主鍵沖突異常,則捕獲異常,接著直接返回成功。

流程圖如下

我們一起聊聊冪等設計

偽代碼如下:

  1. /**
  2. * 冪等處理
  3. */
  4. Rsp idempotent(Request req){
  5. Object requestRecord =selectByBizSeq(bizSeq);
  6. if(requestRecord !=null){
  7. //攔截是重復請求
  8. log.info("重復請求,直接返回成功,流水號:{}",bizSeq);
  9. return rsp;
  10. }
  11. try{
  12. insert(req);
  13. }catch(DuplicateKeyException e){
  14. //攔截是重復請求,直接返回成功
  15. log.info("主鍵沖突,是重復請求,直接返回成功,流水號:{}",bizSeq);
  16. return rsp;
  17. }
  18. //正常處理請求
  19. dealRequest(req);
  20. return rsp;
  21. }

為什么前面已經select查詢了,還需要try...catch...捕獲重復異常呢?

是因為高并發場景下,兩個請求去select的時候,可能都沒查到,然后都走到insert的地方啦。

當然,用唯一索引代替數據庫主鍵也是可以的哈,都是全局唯一的ID即可。

5.2. 直接insert + 主鍵/唯一索引沖突

在5.1方案中,都會先查一下流水表的交易請求,判斷是否存在,然后不存在再插入請求記錄。如果重復請求的概率比較低的話,我們可以直接插入請求,利用主鍵/唯一索引沖突,去判斷是重復請求。

流程圖如下:

我們一起聊聊冪等設計

偽代碼如下:

  1. /**
  2. * 冪等處理
  3. */
  4. Rsp idempotent(Request req){
  5. try{
  6. insert(req);
  7. }catch(DuplicateKeyException e){
  8. //攔截是重復請求,直接返回成功
  9. log.info("主鍵沖突,是重復請求,直接返回成功,流水號:{}",bizSeq);
  10. return rsp;
  11. }
  12. //正常處理請求
  13. dealRequest(req);
  14. return rsp;
  15. }

溫馨提示 :

大家別搞混哈,防重和冪等設計其實是有區別的。防重主要為了避免產生重復數據,把重復請求攔截下來即可。而冪等設計除了攔截已經處理的請求,還要求每次相同的請求都返回一樣的效果。不過呢,很多時候,它們的處理流程可以是類似的。

5.3 狀態機冪等

很多業務表,都是有狀態的,比如轉賬流水表,就會有0-待處理,1-處理中、2-成功、3-失敗狀態。轉賬流水更新的時候,都會涉及流水狀態更新,即涉及狀態機 (即狀態變更圖)。我們可以利用狀態機實現冪等,一起來看下它是怎么實現的。

比如轉賬成功后,把處理中的轉賬流水更新為成功狀態,SQL這么寫:

  1. update transfr_flow set status=2 where biz_seq=‘666’ and status=1;

簡要流程圖如下:

我們一起聊聊冪等設計

偽代碼實現如下:

  1. Rsp idempotentTransfer(Request req){
  2. String bizSeq = req.getBizSeq();
  3. int rows= "update transfr_flow set status=2 where biz_seq=#{bizSeq} and status=1;"
  4. if(rows==1){
  5. log.info(“更新成功,可以處理該請求”);
  6. //其他業務邏輯處理
  7. return rsp;
  8. }else if(rows==0){
  9. log.info(“更新不成功,不處理該請求”);
  10. //不處理,直接返回
  11. return rsp;
  12. }
  13. log.warn("數據異常")
  14. return rsp:
  15. }

狀態機是怎么實現冪等的呢?

第1次請求來時,bizSeq流水號是 666,該流水的狀態是處理中,值是 1,要更新為2-成功的狀態,所以該update語句可以正常更新數據,sql執行結果的影響行數是1,流水狀態最后變成了2。

第2請求也過來了,如果它的流水號還是 666,因為該流水狀態已經2-成功的狀態了,所以更新結果是0,不會再處理業務邏輯,接口直接返回。

5.4 抽取防重表

5.1和5.2的方案,都是建立在業務流水表上bizSeq的唯一性上。很多時候,我們業務表唯一流水號希望后端系統生成,又或者我們希望防重功能與業務表分隔開來,這時候我們可以單獨搞個防重表。當然防重表也是利用主鍵/索引的唯一性,如果插入防重表沖突即直接返回成功,如果插入成功,即去處理請求。

5.5 token令牌

token 令牌方案一般包括兩個請求階段:

客戶端請求申請獲取token,服務端生成token返回

客戶端帶著token請求,服務端校驗token

流程圖如下:

我們一起聊聊冪等設計

客戶端發起請求,申請獲取token。

服務端生成全局唯一的token,保存到redis中(一般會設置一個過期時間),然后返回給客戶端。

客戶端帶著token,發起請求。

服務端去redis確認token是否存在,一般用 redis.del(token)的方式,如果存在會刪除成功,即處理業務邏輯,如果刪除失敗不處理業務邏輯,直接返回結果。

5.6 悲觀鎖(如select for update)

什么是悲觀鎖?

通俗點講就是很悲觀,每次去操作數據時,都覺得別人中途會修改,所以每次在拿數據的時候都會上鎖。官方點講就是,共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程。

悲觀鎖如何控制冪等的呢?就是加鎖呀,一般配合事務來實現。

舉個更新訂單的業務場景:

假設先查出訂單,如果查到的是處理中狀態,就處理完業務,再然后更新訂單狀態為完成。如果查到訂單,并且是不是處理中的狀態,則直接返回

整體的偽代碼如下:

  1. begin; # 1.開始事務
  2. select * from order where order_id='666' # 查詢訂單,判斷狀態
  3. if(status !=處理中){
  4. //非處理中狀態,直接返回;
  5. return ;
  6. }
  7. ## 處理業務邏輯
  8. update order set status='完成' where order_id='666' # 更新完成
  9. commit; # 5.提交事務

這種場景是非原子操作的,在高并發環境下,可能會造成一個業務被執行兩次的問題:

當一個請求A在執行中時,而另一個請求B也開始狀態判斷的操作。因為請求A還未來得及更改狀態,所以請求B也能執行成功,這就導致一個業務被執行了兩次。

可以使用數據庫悲觀鎖(select ...for update)解決這個問題.

  1. begin; # 1.開始事務
  2. select * from order where order_id='666' for update # 查詢訂單,判斷狀態,鎖住這條記錄
  3. if(status !=處理中){
  4. //非處理中狀態,直接返回;
  5. return ;
  6. }
  7. ## 處理業務邏輯
  8. update order set status='完成' where order_id='666' # 更新完成
  9. commit; # 5.提交事務

這里面order_id需要是索引或主鍵哈,要鎖住這條記錄就好,如果不是索引或者主鍵,會鎖表的!

悲觀鎖在同一事務操作過程中,鎖住了一行數據。別的請求過來只能等待,如果當前事務耗時比較長,就很影響接口性能。所以一般不建議用悲觀鎖做這個事情。

5.7 樂觀鎖

悲觀鎖有性能問題,可以試下樂觀鎖。

什么是樂觀鎖?

樂觀鎖在操作數據時,則非常樂觀,認為別人不會同時在修改數據,因此樂觀鎖不會上鎖。只是在執行更新的時候判斷一下,在此期間別人是否修改了數據。

怎樣實現樂觀鎖呢?

就是給表的加多一列version版本號,每次更新記錄version都升級一下(version=version+1)。具體流程就是先查出當前的版本號version,然后去更新修改數據時,確認下是不是剛剛查出的版本號,如果是才執行更新

比如,我們更新前,先查下數據,查出的版本號是version =1

  1. select order_id,version from order where order_id='666'

然后使用version =1和訂單Id一起作為條件,再去更新

  1. update order set version = version +1,status='P' where order_id='666' and version =1

最后更新成功,才可以處理業務邏輯,如果更新失敗,默認為重復請求,直接返回。

流程圖如下:

我們一起聊聊冪等設計

為什么版本號建議自增的呢?

因為樂觀鎖存在ABA的問題,如果version版本一直是自增的就不會出現ABA的情況啦。

5.8 分布式鎖

分布式鎖實現冪等性的邏輯就是,請求過來時,先去嘗試獲得分布式鎖,如果獲得成功,就執行業務邏輯,反之獲取失敗的話,就舍棄請求直接返回成功。執行流程如下圖所示:

我們一起聊聊冪等設計

分布式鎖可以使用Redis,也可以使用ZooKeeper,不過還是Redis相對好點,因為較輕量級。

Redis分布式鎖,可以使用命令SET EX PX NX + 唯一流水號實現,分布式鎖的key必須為業務的唯一標識哈

Redis執行設置key的動作時,要設置過期時間哈,這個過期時間不能太短,太短攔截不了重復請求,也不能設置太長,會占存儲空間。

6. HTTP的冪等

我們的接口,一般都是基于http的,所以我們再來聊聊Http的冪等吧。HTTP 請求方法主要有以下這幾種,我們看下各個接口是否都是冪等的。

  • GET方法
  • HEAD方法
  • OPTIONS方法
  • DELETE方法
  • POST 方法
  • PUT方法

6.1 GET 方法

HTTP 的GET方法用于獲取資源,可以類比于數據庫的select查詢,不應該有副作用,所以是冪等的。它不會改變資源的狀態,不論你調用一次還是調用多次,效果一樣的,都沒有副作用。

如果你的GET方法是獲取最近最新的新聞,不同時間點調用,返回的資源內容雖然不一樣,但是最終對資源本質是沒有影響的哈,所以還是冪等的。

6.2 HEAD 方法

HTTP HEAD和GET有點像,主要區別是HEAD不含有呈現數據,而僅僅是HTTP的頭信息,所以它也是冪等的。如果想判斷某個資源是否存在,很多人會使用GET,實際上用HEAD則更加恰當。即HEAD方法通常用來做探活使用。

6.3 OPTIONS方法

HTTP OPTIONS 主要用于獲取當前URL所支持的方法,也是有點像查詢,因此也是冪等的。

6.4 DELETE方法

HTTP DELETE 方法用于刪除資源,它是的冪等的。比如我們要刪除id=666的帖子,一次執行和多次執行,影響的效果是一樣的呢。

6.5 POST 方法

HTTP POST 方法用于創建資源,可以類比于提交信息,顯然一次和多次提交是有副作用,執行效果是不一樣的,不滿足冪等性。

比如:POST http://www.tianluo.com/articles的語義是在http://www.tianluo.com/articles下創建一篇帖子,HTTP 響應中應包含帖子的創建狀態以及帖子的 URI。兩次相同的POST請求會在服務器端創建兩份資源,它們具有不同的 URI;所以,POST方法不具備冪等性。

6.6 PUT 方法

HTTP PUT 方法用于創建或更新操作,所對應的URI是要創建或更新的資源本身,有副作用,它應該滿足冪等性。

比如:PUT http://www.tianluo.com/articles/666的語義是創建或更新 ID 為666的帖子。對同一 URI 進行多次 PUT 的副作用和一次 PUT 是相同的;因此,PUT 方法具有冪等性。

參考資料

[1]彈力設計篇之“冪等性設計”: https://time.geekbang.org/column/article/4050

原文鏈接:https://mp.weixin.qq.com/s/P3GVyHxrSLN4FV2xwnP71g

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 91精品啪啪| 日日摸夜夜添夜夜添特色大片 | 国产精品一区二区三区四区 | 色站综合| 国产精品久久久久一区二区三区 | 久久久久综合 | 欧美成人免费视频 | 亚洲网色| av在线黄 | 99这里只有精品视频 | 亚洲精品免费在线 | 日本久草 | 成人免费xxxxx在线视频软件 | 亚洲成人福利 | 国产精品99久久久久久宅男 | 久久久精品国产 | 视频一区二区三区中文字幕 | 激情网页 | 狠狠人人| 日韩欧美在线免费观看 | 中文字幕日韩在线 | 一级片免费观看 | 久久逼逼 | 欧美成人精品一区二区三区在线看 | 亚洲自啪 | 在线观看国产视频 | 欧美伊人| 中文字幕一区二区三区四区 | 香蕉久久一区二区不卡无毒影院 | 一级在线观看视频 | 亚洲a网 | 精品少妇一区二区三区日产乱码 | 亚洲一区二区三区四区在线 | 午夜影院黄色 | 激情综合五| 亚洲视频中文字幕在线观看 | 99re6在线视频精品免费 | 每日更新av | 日韩中文字幕在线播放 | 日韩 欧美 中文 | 欧美国产日韩在线 |