冪等公共組件

来源:https://www.cnblogs.com/killbug/archive/2022/08/27/16630315.html
-Advertisement-
Play Games

前言 今天想聊一聊冪等相關的知識,以及實現一個冪等公共組件需要重點涉及和思考的點。 概念 首先,什麼是冪等,在實際代碼生產過程中有什麼作用呢? 在編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。 舉個例子,假如有個方法,用於修改一個訂單的狀態為已完成,只改一個狀態欄位,要 ...


前言

今天想聊一聊冪等相關的知識,以及實現一個冪等公共組件需要重點涉及和思考的點。

概念

首先,什麼是冪等,在實際代碼生產過程中有什麼作用呢?

在編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。

舉個例子,假如有個方法,用於修改一個訂單的狀態為已完成,只改一個狀態欄位,要達到冪等的效果我們可以這樣:

  • 每次執行都正真執行更新語句介面,結果都是狀態保持已完成
  • 每次執行先判斷訂單狀態是否已經是已更新狀態,如果是就返回,如果不是就執行更新語句,也過也是保持已完成狀態

所以,一個擁有冪等性的業務代碼,就可以保證外部重覆調用的結果和單次調用的結果一致,保證這一點在實際代碼生產中的一些場景中是非常重要的。以下做一些例舉:

  • 客戶端併發重覆提交,這種比較常見,用戶連續點擊按鈕即可觸發重覆提交
  • 微服務架構中Http或RPC請求調用失敗觸發重試
  • 消息中間件重覆消費,消息中間件本身就是通過重覆消費達到業務解耦和一致性的,所以使用消息是必然需要考慮冪等情況的
  • 調用方定時任務重覆調用或者上游觸發歷史業務請求,從不信任外部的角度看,有時也需要考慮

總結一下:

At least once + 冪等 = exactly once

邏輯

一下是使用較多的冪等方案的流程圖如下:

image

  • 一個微服務系統中在進入業務執行前必須要保證拿到分散式鎖,這樣才能屏蔽掉併發重覆請求
  • 執行業務和冪等標記的存儲需要保證原子性,才能保證不會出現冪等標記和業務變更的數據不一致情況的發生,否則這個冪等標記就沒有意義了

公共組件

公共組件的例子以Spring為基礎,其中會使用到Spring相關的組件。

設計

根據前面的流程圖,使用AOP非常適合實現一個公共組件。

image

代碼

定義註解提供給業務使用:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {

   /**
    * Name used to determine the target idempotent key prefix
    */
   String name();

   /**
    * support Spring Expression Language (SpEL)
    */
   String key();

   /**
    * set idempotent key expire time, default 0 second
    */
   long idempotentExpire() default 0;

   /**
    * set lock expire time default 60 seconds
    */
   long lockExpire() default 60;

}
  • 註解中的信息包含冪等key,鎖過期時間,冪等保護時間,註意這裡的key支持SpEL,這樣就可以非常方便的可以把方法中的參數作為key的一部分

通過註解切麵,核心代碼如下:

public <T> T execute(IdempotentRequest request, Supplier<T> processSupplier, Supplier<T> failSupplier) {
    String idempotentKey = request.getKey();
    long idempotentExpire = request.getIdempotentExpire();
    long lockExpire = request.getLockExpire() == 0 ? DEFAULT_LOCK_TIME : request.getLockExpire();
    IdempotentRecordStorage idempotentRecordStorage = getIdempotentRecordStorage(idempotentExpire);
    try {
        boolean locked = redisLockService.lock(idempotentKey, LOCK_VALUE, lockExpire, true);
        if (locked) {
            if (idempotentRecordStorage.hasKey(idempotentKey)) {
                return failSupplier.get();
            }
            T result = processSupplier.get();
            idempotentRecordStorage.setKey(idempotentKey, idempotentExpire);
            return result;
        } else {
            return failSupplier.get();
        }
    } finally {
        redisLockService.unlock(idempotentKey, LOCK_VALUE, true);
    }

}

看一下Oracle 的實現例子:

public class OracleStorage implements IdempotentRecordStorage {

    private JdbcTemplate jdbcTemplate;

    public OracleStorage(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void setKey(String key, long expire) {
        Date expireDate = expire == 0 ? null : new Date(System.currentTimeMillis() + expire * 1000);
        String sql = "insert into AAP_IDEMPOTENT_RECORD(ID, KEY, CREATE_TIME, EXPIRE_TIME) values(IDEMPOTENT_RECORD_SEQUENCE.nextval, ?,?,?)";
        jdbcTemplate.update(sql, key, new Date(), expireDate);
    }

    @Override
    public boolean hasKey(String key) {
        String sql = "select count(1) from AAP_IDEMPOTENT_RECORD WHERE KEY = ?";
        Integer value = jdbcTemplate.queryForObject(sql, Integer.class, key);
        return value > 0;
    }

    @Override
    public StorageTypeEnum getType() {
        return StorageTypeEnum.ORACLE;
    }

}

思考

在實際coding的過程中有幾個有意思的點:

  • 1,想把冪等記錄操作和業務操作放入一個事務內,才能保證前面圖中的原子操作,而一般我們會使用@Transaction註解,這個註解也和我們一樣使用AOP實現,所以問題就來了,兩個切麵的順序性需要做準確的調整,因為我的例子項目里沒有設置@Transaction切麵的order,所以預設是Integer.MAX_VALUE,自定義的切麵也預設是Integer.MAX_VALUE,所以就出現了@Transaction註解在內層,導致變成兩個事務的提交,而不能保證原子性。調整順序方式:@EnableTransactionManagement(order = Ordered.LOWEST_PRECEDENCE - 100)
  • 2,當我以為業務操作和冪等操作在一個事務的時候我產生了一個疑惑,冪等操作自己提前會先提交嗎?如果會的話,那又保證不了原子了。這裡註意使用的是jdbcTemplate,底層還是會和@Transaction註解一樣拿到相同的Connection,所以可以達到一起提交的能力。
  • 3,如果使用Redis做冪等數據的操作,那麼就需要額外考慮保證原子性的方法,比如在setKey的位置實際執行成功,但是返回網路問題拋出異常,前面業務操作的事會被回滾,但是冪等數據實際已經存在的問題。為瞭解決這個問題,更傾向於提供給使用方決定何種情況下需要清楚冪等數據。這裡代碼沒有提供,需要補充。

以上是個人的一些思考,實現代碼放在github,歡迎交流:
https://github.com/dchack/crab


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 什麼是電腦 電腦俗稱‘電腦’,通電的人腦。其實,電腦所有的組成部分,都是模仿人的某一個功能或者器官。 2. 為什麼要有電腦 為了執行人類通過編程語言編寫的文件程式,從而把人類解放出來。 3.電腦的組成部分 電腦有五大組成部分:控制器、運算器、存儲器、輸入設備、輸出設備。 3.1 控 ...
  • 創作不易,多多支持! 再說此函數之前,先來說一下EOF是什麼 EOF,為End Of File的縮寫,通常在文本的最後存在此字元表示資料結束。 在C語言中,或更精確地說成C標準函式庫中表示文件結束符。這種以EOF作為文件結束標誌的文件,必須是文本文件。在文本文件中,數據都是以字元的ASCII代碼值的 ...
  • 學習Lambda的理由 絕大多數公司代碼的主流風格。 大數據量下處理集合效率高,優秀的高併發解決。 代碼的可讀性增強。 消滅嵌套地獄。>形狀的if或者for再也不用寫了。 為了瞭解Lambda表達式,我們必須瞭解什麼是函數式介面,這是Lambda表達式得以實現的依據。 在java中,函數式介面指註解 ...
  • 不知不覺,python自學教程已經更新到第八篇了,再有幾篇,基本的語法就介紹完了。 今天來總結一下數據類型有哪些需要註意的地方。 元組註意事項 元組是另一種經常使用到的數據類型,看上去和列表差不多。它們之間的區別在於列表是一個可變的數據類型,而元組是不可變的。 #元組a = (1, 2)#列表a = ...
  • 文件操作的模式 文件操作的模式如下表: 1. open 打開文件 使用 open 打開文件後一定要記得調用文件對象的 close() 方法。比如可以用 try/finally 語句來確保最後能關閉文件。 file_object = open(r'D:\test.txt') # 打開文件 try: a ...
  • 最近有許多打工人都在吐槽打工好難 每天都是執行許多重覆的任務 例如閱讀新聞、發郵件、查看天氣、打開書簽、清理文件夾等等, 使用自動化腳本,就無需手動一次又一次地完成這些任務, 非常方便啊有木有?! 而在某種程度上,Python 就是自動化的代名詞。 今天就來和大家一起學習一下, 用8個python自 ...
  • 1. 基礎函數 序號 函數 說明 1 print() 列印 2 input() 輸入 3 int() 轉化為整形 4 float() 轉化為浮點型 5 str() 轉化為字元串 6 type() 返回對象類型 7 isinstance() 判斷對象類型(返回布爾值) 2. 流程式控制制 序號 函數 說明 ...
  • 目錄 一.OpenGL ES 圖像亮度調節 1.原始圖片 2.效果演示 二.OpenGL ES 圖像亮度調節源碼下載 三.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路線推薦 : OpenGL ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...