1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一文理解如何實現(xiàn)接口的冪等性

        共 2993字,需瀏覽 6分鐘

         ·

        2021-06-17 13:02

        冪等,這個詞來源自數(shù)學領(lǐng)域。冪等性衍生到軟件工程中,它的語義是指:函數(shù)/接口可以使用相同的參數(shù)重復(fù)執(zhí)行, 不應(yīng)該影響系統(tǒng)狀態(tài),也不會對系統(tǒng)造成改變。

        舉一個簡單的例子:正常設(shè)計的查詢接口,不管調(diào)用多少次,都不會破壞當前的系統(tǒng)或數(shù)據(jù),這就是一個冪等操作。

        冪等的業(yè)務(wù)場景

        在分布式系統(tǒng)中, 由于分布式天然特性的時序問題以及網(wǎng)絡(luò)的不可靠性(機器、機架、機房故障、電纜被挖斷等等), 重復(fù)請求很常見,接口冪等性設(shè)計就顯得尤為重要。

        冪等需要考慮的場景有很多,例如系統(tǒng)A是處理用戶客戶端發(fā)送過來的請求,無論是前端bug、腳本惡意發(fā)包、用戶重復(fù)點擊又或是網(wǎng)絡(luò)超時導(dǎo)致的網(wǎng)絡(luò)重發(fā),都會造成系統(tǒng)A收到相同參數(shù)的網(wǎng)絡(luò)請求。

        對于處理消息隊列請求的系統(tǒng)B和處理服務(wù)上游發(fā)送請求的系統(tǒng)C,也都存在網(wǎng)絡(luò)超時導(dǎo)致的網(wǎng)絡(luò)重發(fā),所以要考慮接口的冪等性。

        保障冪等性的原理

        對于分布式系統(tǒng)來說,在JVM層面的鎖已經(jīng)失去作用,所以保證系統(tǒng)冪等性需要滿足3個條件:

        1. 請求唯一標識:每一個請求必須有一個唯一標識。

        2. 處理唯一標識:每次處理完請求之后,必須有一個記錄標識這個請求處理過了。

        3. 邏輯判斷處理:每次接收請求需要進行判斷之前是否處理過的邏輯處理。根據(jù)請求唯一標識查詢是否存在處理唯一標識。

        實際執(zhí)行中要結(jié)合自身業(yè)務(wù)。

        冪等性實現(xiàn)方案

        1. token機制

        針對客戶端重復(fù)連續(xù)多次點擊的情況,例如用戶購物提交訂單,提交訂單的接口就可以通過token機制實現(xiàn)防止重復(fù)提交。

        主要流程就是:

        1. 服務(wù)端提供生成請求token的接口。在存在冪等問題的業(yè)務(wù)執(zhí)行前,向服務(wù)器獲取請求token,服務(wù)器會把token保存到Redis中。

        2. 然后調(diào)用業(yè)務(wù)接口請求時,把請求token攜帶過去,一般放在請求頭部。

        3. 服務(wù)器判斷請求token是否存在redis中:存在則表示第一次請求,這時把Redis中的token刪除,繼續(xù)執(zhí)行業(yè)務(wù);如果判斷token不存在redis中,就表示是重復(fù)操作,直接返回重復(fù)標記給client,這樣就保證了業(yè)務(wù)代碼,不被重復(fù)執(zhí)行。

        這里要結(jié)合業(yè)務(wù)考慮這種場景:如果請求處理失敗,前端是否需要重新申請token進行重試(因為此時token在服務(wù)端已經(jīng)被刪除)。

        2. 數(shù)據(jù)庫唯一索引

        往數(shù)據(jù)庫表里插入數(shù)據(jù)的時候,利用數(shù)據(jù)庫的唯一索引特性,保證唯一的邏輯。唯一序列號可以是一個字段,例如訂單的訂單號,也可以是多字段的唯一性組合。

        事務(wù)中包含多表數(shù)據(jù)的更新,業(yè)務(wù)要考慮處理事務(wù)回滾的問題。

        3. Redis實現(xiàn)

        Redis實現(xiàn)的方式就是將唯一序列號作為Key存入Redis,在請求處理之前,先查看Key是否存在。唯一序列號可以是一個字段,例如訂單的訂單號,也可以是多字段的唯一性組合。當然這里需要設(shè)置一個key的過期時間,否則Redis中會存在過多的key。具體校驗流程如下圖所示:

        如果想要基于Redis實現(xiàn)冪等性防重框架,需要考慮如下兩個問題:

        1. 如果第一次請求失敗了,客戶端重試,是否需要放行?

        2. 網(wǎng)絡(luò)請求可能是get或者post(內(nèi)部rpc協(xié)議除外),唯一序列號參數(shù)可能在url或是在body體里。則使用防重框架的新接口以及之前老業(yè)務(wù)接口能否做到版本兼容性?

        建議業(yè)務(wù)使用方最好針對指定業(yè)務(wù)進行Redis的冪等方案。

        Zookeeper同樣也能實現(xiàn)上述功能,但由于Zookeeper是CP模型,性能不如Redis,另外針對防重場景,也并不需要Zookeeper高可靠性,所以優(yōu)先推薦Redis。

        4. ON DUPLICATE KEY UPDATE

        有些業(yè)務(wù)場景是先根據(jù)索引從表中查詢數(shù)據(jù)是否存在,如果存在則更新狀態(tài),不存在才插入數(shù)據(jù)。

        這種情況下在并發(fā)量不大的時候沒有問題,但是在高并發(fā)場景,可能會出現(xiàn)同時插入兩條相同索引的情況,導(dǎo)致"Duplicate entry for key 'PRIMARY'"問題。

        解決方法首先想到的當然是分布式鎖。但分布式鎖降低了吞吐量而且分布式鎖依賴的組件,如Zookeeper或Redis如果出現(xiàn)網(wǎng)絡(luò)超時,同樣會影響在線服務(wù)。

        所以一個簡單的解決方法是使用mysql的INSERT INTO ...ON DUPLICATE KEY UPDATE語法,從而保證了接口的冪等性。

        5. 狀態(tài)機

        對于很多業(yè)務(wù),都存在業(yè)務(wù)流轉(zhuǎn)狀態(tài)的,每個狀態(tài)都有前置狀態(tài)以及最后的結(jié)束狀態(tài)。

        以訂單為例,已支付的狀態(tài)的前置狀態(tài)只能是待支付,而取消狀態(tài)的前置狀態(tài)只能是待支付,通過這種狀態(tài)機的流轉(zhuǎn)就可以控制請求的冪等。假設(shè)當前狀態(tài)是已支付,這時候如果支付接口又接收到了支付請求,則會拋異常或拒絕此次請求。


        public enum OrderStatusEnum {
        UN_SUBMIT(0, 0, "待提交"),
        UN_PADING(0, 1, "待支付"),
        PAYED(1, 2, "已支付待發(fā)貨"),
        DELIVERING(2, 3, "已發(fā)貨"),
        COMPLETE(3, 4, "已完成"),
        CANCEL(0, 5, "已取消"),
        ;

        //前置狀態(tài)
        private int preStatus;

        //狀態(tài)值
        private int status;

        //狀態(tài)描述
        private String desc;

        OrderStatusEnum(int preStatus, int status, String desc) {
        this.preStatus = preStatus;
        this.status = status;
        this.desc = desc;
        }
        //...
        }

        6. MVCC方案

        這個方案嚴格上并不是解決冪等問題,更確切來說是解決并發(fā)問題。但高并發(fā)場景下,也是一種必須的保障措施。

        多版本并發(fā)控制,該策略主要使用update with condition來保證多次外部請求調(diào)用對系統(tǒng)的影響是一致的。在系統(tǒng)設(shè)計的過程中,合理的使用樂觀鎖,通過version或者updateTime(timestamp)等其他條件,來做樂觀鎖的判斷條件,這樣保證更新操作即使在并發(fā)的情況下,也不會有太大的問題。例如

        select * from tablename where condition=@condition //取出要更新的對象,帶有版本versoin  
        update tableName set name=#name#,version=version+1 where version=@version

        在更新的過程中利用version來防止其他操作對對象的并發(fā)更新。如果直接拒絕是不理想的操作,則服務(wù)端需要一定的事務(wù)回滾與重試機制。

        7. 分布式鎖

        有關(guān)分布式鎖的講解,可以查看博客《一文理解分布式鎖的實現(xiàn)方式

        分布式鎖同樣可以實現(xiàn)接口的冪等性,但由于分布式鎖對系統(tǒng)負擔來說相對要重一些,可以結(jié)合業(yè)務(wù)場景進行技術(shù)選型。

        參考文檔:

        1. https://zh.wikipedia.org/wiki/冪等


        瀏覽 75
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            极品美女操逼视频 | 人妻操操操 | 丰满护士巨好爽好大乳gif | 做爱网站现在观看免费 | 97 超碰自拍 | 91成人影片 | 日韩黄色在线视频 | 伊人精品网站 | 色香蕉久久 | 欧美做爰猛烈大尺度电影爱恋 |