實現冪等性的幾種方案

1 前言

大家好,我是阿沐!”冪等“這個詞語或許小夥伴很少見,基本上中小型公司或者一些大公司都未使用過,但是並不代表小夥伴們沒有接觸到。

爲啥我會扯到這個技術話題?緣由就是 20 年我面試了一些大廠包括身邊朋友的面試經歷,例如騰訊、網易、字節等等大廠,其中大都會遇到”冪等的概念、理解以及實現與應用“,那麼下面就聽我一一道來冪等的相關知識。

2 什麼是冪等性?

數學中:在一次元運算爲冪等時,其作用在任一元素兩次後會和其作用一次的結果相同;在二次元運算爲冪等時,自己重複運算的結果等於它自己的元素。

計算機學中:冪等指多次操作產生的影響只會跟一次執行的結果相同,通俗的說:某個行爲重複的執行,最終獲取的結果是相同的,不會因爲重複執行對系統造成變化。

3 爲什麼要使用冪等性?

衆所周知,目前隨着 js 的發展 web 端慢慢轉變了前後端分離,通過接口實現;小程序、app 同樣都是 api 實現,基本上接口都是正常的返回信息,並未涉及到重複提交或者是併發提交的情況。但假如我們考慮的細緻一點,比如電商系統,抽獎活動、用戶反饋、訂單支付、消息消費、商品評價、商品點贊等這些都是和冪等息息相關。舉幾個例子給小夥伴們看下:

  • ① 用戶重複下訂單:當用戶下單時,因爲網絡問題或者手速過快,導致重複下單。
  • ② 消息重複消費:當使用 MQ 消息中間件時候,如果消息中間件發生異常出現錯誤未及時提交消費信息,導致消息被重複消費。
  • 抽獎活動(券):當用戶參加抽獎活動需要消耗抽獎券時,如果出現併發請求導致抽獎券餘額更新錯誤。
  • 重複提交表單:當用戶填寫表單提交時,可能會因爲用戶點多次連擊提交或者網絡波動導致服務端未及時響應,會導致用戶重複的提交表單,就出現了同一個表單多次請求。

這些只是我們常見的一些狀況,還需要根據自己的項目的實際情況進行分析,判斷是否需要冪等操作,舉個簡單例子:運營做了一次大型活動,參與人數 10w+(每人只能給一個用戶點贊衝榜),活動結束後運營需要覆盤,這個時候發現一些用戶給一個人點贊又多次狀況。這個時候,嘿嘿嘿...... 開發過來背鍋嘍!

4 我們如何在業務功能上實現冪等性?

通常數據庫實現主要是利用數據庫表中主鍵唯一約束+唯一索引的特性,如果主鍵唯一或者設置了複合唯一索引,在”插入“數據的時候就是冪等性操作。例如還有悲觀鎖、樂觀鎖、redis 鎖、token 令牌、依賴前後端配合生成請求序列號。ps:基本上面試時,大廠都會被問到的問題,不要問我爲什麼?因爲面試官喜歡問。

下面是設計一個活動抽獎大轉盤用戶抽獎券餘額:抽獎券來源參加活動獲取,抽獎消耗券場景:

CREATE TABLE `mumu_test` (  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵自增id',  `userid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用戶id',  `act_id` varchar(125) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '活動ID',  `lottery` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '餘額',  PRIMARY KEY (`id`),  UNIQUE KEY `uniq_uid_aid` (`userid`,`act_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='沐沐測試春節抽獎券記錄表';

 

唯一主鍵索引實現冪等性

通常情況下,我們在做這種用戶活動抽獎券記錄數據時,會先 select 下看看是否已經有插入的記錄了,如果已存在則 update,否則 insert。那麼我現在先說說不存在添加數據的情況:

存在用戶在做活動任務時,因爲網絡抖動導致服務端響應超時,這個時候用戶以爲並沒領取獎券成功,就會瘋狂的點擊領取按鈕,那麼就會導致同一個任務獎券出現多次請求,那麼我們第一次 insert 添加肯定成功了,當併發請求過來時就會重複執行以下 sql 語句:

inser into mumu_test('userid','act_id','lottery')values(123,'spring',1)

由於存在 userid+act_id 唯一鍵,那麼就會出現只有一條數據插入成功,其他的數據就會插入失敗,保證了數據的冪等。推薦使用

樂觀鎖實現冪等性

通俗地講:它的心態就是很樂觀,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在提交更新的時候會判斷一下在此期間別人有沒有去更新這個數據。它就像單純的孩子一樣,總是認爲不會產生併發衝突的場景,我只是在你提交操作時檢查是否違反數據完整性。所以樂觀鎖適用於讀多寫少的應用場景,這樣可以提高吞吐量。

劃重點:一般使用版本號控制 version,即爲數據增加一個版本標識,一般是通過爲數據庫錶行數據增加一個數字類型的“version”字段來實現。當讀取數據時,會將 version 字段的值一同讀出,數據每更新一次,對此 version 值加 1 操作。當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的 version 值進行比對,如果數據庫表當前版本號與第一次取出來的 version 值相等,則予以更新,否則認爲是非法操作。

場景應用:針對上面的表我們增加一個版本標識:

alter table mumu_test add `version` smallint(5) unsigned not null default '0' comment '版本號'

添加成功之後,更新操作:

1.獲取抽獎券:update mumu_test set lottery = lottery + 5, version = version + 1 where id = 123 and version = 1;
2.消耗抽獎券update mumu_test set lottery = lottery - 1, version = version + 1 where id = 123 and version = 2;

實現原理:更新數據的同時 version+1,然後判斷本次 update 操作的影響行數,如果大於 0,則說明本次更新成功,如果等於 0,則說明本次更新沒有讓數據變更。當併發請求過來時,只需要拿到 select 的版本號,進行更新操作即可(where 可帶上主鍵 id),保證冪等。推薦使用

悲觀鎖實現冪等性

顧名思義,悲觀鎖它是一種悲觀的心裏狀態,對應於生活中悲觀的人總是想着事情往壞的方向發展。它像是一個徹底地 loser,它認爲別人每次去拿數據都會修改這條數據,所以每次拿數據的時候,都會使數據處於鎖定狀態。執行下面 sql 語句鎖住該條記錄:

select * from mumu_test where userid = 123 and act_id = 'spring' for update;

大家可以看到我並沒有使用主鍵 id 是查詢,首先我們並不知道這條記錄 id 值,所以我們通過 uid+aid 組合的唯一建作爲鎖錶行記錄條件,一定要使用主鍵或者唯一建,不然會將整張表都被鎖住,那麼其他的用戶就無法操作了。

因爲悲觀鎖是需要在同一個事物操作過程中鎖住一行數據,假如我們事務邏輯耗時比較久,就會導致後面請求的堆積,直接影響到了整體響應時長。不推薦使用

Token 令牌如何實現冪等性

所謂的 token 令牌其實就是爲了防止用戶重複提交一個表單信息,這一點基本上 PHP 的框架都會帶有 token 驗證。服務端需要生成一個全局唯一的 id,(例如:snowflake 雪花算法美團 Leaf 算法滴滴 TinyID 算法百度 Uidgenerator 算法uuidredis 等)。

  1. 客戶端每次進入表單頁面可以優先申請一個唯一令牌存儲本地,服務端存儲令牌 token 值(redis,文件,memcache 都可)
  2. 每次發送請求時可以在 Headers 頭部中帶上當前這個 token 令牌
  3. 服務端驗證 token 是否存在,存在則刪除 token,執行後續業務邏輯;不存在則響應客戶端重複提交提示語

生成全局唯一 id 的代碼,大家可以網上自行搜索,基本上是千篇一律的,放心抄過來使用就可以了。

最後總結

冪等性基本上在中大型公司項目需求中都能遇到,尤其是現在消息中間件(kafka、rabbitmq 等)的廣泛使用,更加註重消息的冪等。那麼像我之前在電商公司,支付訂單、抽獎券、部分活動相關的中臺服務對接口的冪等性都是很重要的,所以我們在日常開發中,可以針對不同的業務場景選擇合適的冪等方案,即可滿足要求同時也減少性能影響,更重要的是不會因爲出 bug 被產品運營 diss。開發真的好卑微啊~~~

聊冪等性這個詞語,也是自己想了很久。在這之前我推薦不少開發(經驗基本上 5 年+)到大廠,他們給的反饋就有冪等這個概念的詢問。ps:當然並不說明他們能力不行、技術差,冪等名詞一般大家很少去注意,尤其是常年待在一個公司,並不一定能接觸到冪等業務,就算接觸到了類似場景;而都是認爲對於一致性的要求不能重複插入數據,並不會想起是冪等這個名詞。所以並不奇怪,大家也不要在面試中遇到新的名詞就內心慌亂,手心出汗、腿發抖、發冷汗,我們完全可以跟面試官聊,是否可以換一種方式來問這個問題;我相信大部分的面試官都能接受,頂多就認爲你知識量不夠廣,不知道這些專業術語等等,不會太影響你後面的解決思路。

 

參考:

https://cloud.tencent.com/developer/news/838221

======

補充:

實現冪等性的幾種方案:

1.select+insert+主鍵/唯一索引衝突

2.直接insert + 主鍵/唯一索引衝突

3.狀態機冪等

4.抽取防重表

5.token令牌

6.悲觀鎖(如select for update)

7.樂觀鎖

8.分佈式鎖

======

實現分佈式鎖的幾種方案:

1.數據庫。(唯一索引,樂觀鎖,悲觀鎖)

2.redis。

3.zookeeper。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章