冪等性是什么?
冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。冪等函數,或冪等方法,是指可以使用相同參數重復執行,並能獲得相同結果的函數。這些函數不會影響系統狀態,也不用擔心重復執行會對系統造成改變。
接口為什么要實現冪等?
前端重復提交選中的數據,后台只產生對應這個數據的一個反應結果。
冪等常用思路
token機制
當客戶端請求頁面時,服務器會生成一個隨機數token,並且將token放置到session當中,然后將token發給客戶端(一般通過構造hidden表單)。下次客戶端提交請求時,token會隨着表單一起提交到服務器端。服務器端第一次驗證相同過后,會將session中的token值更新下,若用戶重復提交,第二次的驗證判斷將失敗,因為用戶提交的表單中的token沒變,但服務器端session中token已經改變了。
樂觀鎖
通過版本號實現
update table_xxx set name=#name#,version=version+1 where version=#version#;通過條件限制 update table_xxx set avai_amount=avai_amount-#subAmount# where avai_amount-#subAmount# >= 0要求:quality-#subQuality# >= ,這個情景適合不用版本號,只更新是做數據安全校驗,適合庫存模型,扣份額和回滾份額,性能更高;
去重表
利用數據庫表單的特性來實現冪等,常用的一個思路是在表上構建唯一性索引。需求是博客點贊問題,要想防止一個人重復點贊,可以設計一張表,將博客id與用戶id綁定建立唯一索引,每當用戶點贊時就往表中寫入一條數據,這樣重復點贊的數據就無法寫入。
我們可以借鑒數據庫的樂觀鎖機制來舉個例子
1、首先為表添加一個版本字段version
2、在執行更新操作前呢,會先去數據庫查詢這個version
3、然后執行更新語句,以version作為條件,例如:
UPDATE T_REPS SET COUNT = COUNT -1,VERSION = VERSION + 1 WHERE VERSION = 1
4、如果執行更新時有其他人先更新了這張表的數據,那么這個條件就不生效了,也就不會執行操作了,通過這種樂觀鎖的機制來保障冪等性。
消費端-冪等性保障
什么情況下會出現重復消費?
當消費者消費完消息時,在給生產端返回ack時由於網絡中斷,導致生產端未收到確認信息,該條消息會重新發送並被消費者消費,但實際上該消費者已成功消費了該條消息,這就是重復消費問題。
如何避免消息的重復消費問題?
消費端實現冪等性,就意味着,我們的消息永遠不會消費多次,即使我們收到了多條一樣的消息
業界主流的冪等性操作:
· 唯一ID + 指紋碼機制,利用數據庫主鍵去重
· 利用Redis的原子性去實現
唯一ID+指紋碼機制
· 唯一ID + 指紋碼機制,利用數據庫主鍵去重
· SELECT COUNT(1) FROM T_ORDER WHERE ID = 唯一ID +指紋碼
· 好處:實現簡單
· 壞處:高並發下有數據庫寫入的性能瓶頸
· 解決方案:跟進ID進行分庫分表進行算法路由
整個思路就是首先我們需要根據消息生成一個全局唯一的ID,然后還需要加上一個指紋碼。這個指紋碼它並不一定是系統去生成的,而是一些外部的規則或者內部的業務規則去拼接,它的目的就是為了保障這次操作是絕對唯一的。
將ID + 指紋碼拼接好的值作為數據庫主鍵,就可以進行去重了。即在消費消息前呢,先去數據庫查詢這條消息的指紋碼標識是否存在,沒有就執行insert操作,如果有就代表已經被消費了,就不需要管了。
對於高並發下的數據庫性能瓶頸,可以跟進ID進行分庫分表策略,采用一些路由算法去進行分壓分流。應該保證ID通過這種算法,消息即使投遞多次都落到同一個數據庫分片上,這樣就由單台數據庫冪等變成多庫的冪等。
利用Redis的原子性去實現
我們都知道redis是單線程的,並且性能也非常好,提供了很多原子性的命令。比如可以使用 setnx 命令。
在接收到消息后將消息ID作為key執行 setnx 命令,如果執行成功就表示沒有處理過這條消息,可以進行消費了,執行失敗表示消息已經被消費了。
使用 redis 的原子性去實現主要需要考慮兩個點
· 第一:我們是否要進行數據落庫,如果落庫的話,關鍵解決的問題是數據庫和緩存如何做到原子性?
· 第二:如果不進行落庫,那么都存儲到緩存中,如何設置定時同步的策略(同步到關系型數據庫)?緩存又如何做到數據可靠性保障呢
關於不落庫,定時同步的策略,目前主流方案有兩種,第一種為雙緩存模式,異步寫入到緩存中,也可以異步寫到數據庫,但是最終會有一個回調函數檢查,這樣能保障最終一致性,不能保證100%的實時性。第二種是定時同步,比如databus同步。
1.使用redis的setnx命令的情況下,如果消費者端setnx成功后,進行消息消費,但是此時突然宕機。那么對於接下來一段時間內(指代鎖的有效時長),就無法保證消息的及時消費?
答:首先宕機問題要盡量避免,通過一些高可用的方案降低宕機的風險,如果確實宕機了,對於已發送但未被消費的消息,可以自己去做補償或者投遞到延遲隊列里處理,宕機會造成生產端消息堆積,如果對消息實時處理要求比較高,需要提前預備一些應急方案另起服務去處理這些消息。
2.redis的setnx怎么做冪等性的? 鎖的有效時長設為多少呢
redis實現冪等很簡單,我以redis實現接口的冪等性為例說明。你可以自定義一個冪等注解,然后配合AOP進行方法攔截,對攔截的請求信息(包括方法名+參數名+參數值)根據固定的規則去生成一個key,然后調用redis的setnx方法,如果返回ok,則正常調用方法,否則就是重復調用了。這樣可以保證重復請求接口在一定時間內只會被成功處理一次。至於鎖的有效時長要根據業務情況而定的。
mq如何保證消息的冪等性
一、出現非冪等性的情況
1、生產者已把消息發送到mq,在mq給生產者返回ack的時候網絡中斷,故生產者未收到確定信息,生產者認為消息未發送成功,但實際情況是,mq已成功接收到了消息,在網絡重連后,生產者會重新發送剛才的消息,造成mq接收了重復的消息
2、消費者在消費mq中的消息時,mq已把消息發送給消費者,消費者在給mq返回ack時網絡中斷,故mq未收到確認信息,該條消息會重新發給其他的消費者,或者在網絡重連后再次發送給該消費者,但實際上該消費者已成功消費了該條消息,造成消費者消費了重復的消息;
二、解決辦法
1、mq接收生產者傳來的消息:
mq內部會為每條消息生成一個全局唯一、與業務無關的消息id,當mq接收到消息時,會先根據該id判斷消息是否重復發送,mq再決定是否接收該消息。
2、消費者消費mq中的消息:
也可利用mq的該id來判斷,或者可按自己的規則生成一個全局唯一id,每次消費消息時用該id先判斷該消息是否已消費過
大家好,我是luke。如果對文章感興趣的話可以·點贊·、收藏一波·,同時也可以關注一下我,謝謝帥氣美麗,魅力四射的你,能夠把文章看完說明你能夠理解一個大概了,此時不應該給努力的自己點一個贊,收藏一波嗎?PHP技術交流群66:1071033649