分散式Id – redis方式

  • 2019 年 10 月 3 日
  • 筆記

本篇分享內容是關於生成分散式Id的其中之一方案,除了redis方案之外還有如:資料庫,雪花演算法,mogodb(object_id也是資料庫)等方案,對於redis來說是我們常用並接觸比較多的,因此主要談談結合redis生成分散式id方案。

  • 分散式Id設計流程圖
  • 基於redis的hash自動increment累加生成有序Id
  • 定期刪除無用hash列

分散式Id設計流程圖(有點粗略)

 

基於redis的hash自動increment累加生成有序Id

使用redis方案生成id,其中之一的方式主要使用increment(遞增),不管是string、hash等都具有該方法,為了更方便管理我們id生成key這裡建議使用hash的列的方式,以下內容都基於springboot分享;

當然,第一步我們需要創建一個hash和hkey才行,至於在業務第一次被訪問來創建這個hash還是通過服務自動創建這個看業務和流量,這裡的hkey是有一定規則的(當然不用局限性),這裡我按照日期格式來做key,可以有如下程式碼:

 1     /**   2      * 生成每天的初始Id   3      * @param hashName   4      * @return   5      */   6     public String initPrimaryId(String hashName) {   7         Assert.hasLength(hashName, "hashName不能為空");   8   9         String hashCol = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));  10         //自定義編號規則  11         String hashColVal = hashCol + "00001";  12         redisTemplate.opsForHash().putIfAbsent(hashName, hashCol, hashColVal);  13         return hashCol;  14     }

上面很容易理解,hash中key是有每天日期格式組成,意思每天都需要生成一個新的日期key,通過putIfAbsent達到不重複添加的原則,至於hval可以根據自定義編號規則來生成一串數字字元(註:一定要數字);有了上面的基礎,我們僅僅需要increment來累加,redis即幫我們完整hval+1的操作,當然可以自定義累加數,如下程式碼:

 1     /**   2      * 獲取分散式Id   3      *   4      * @param hashName   5      * @return   6      */   7     public long getPrimaryId(String hashName) {   8         try {   9             String hashCol = initPrimaryId(hashName);  10             return redisTemplate.opsForHash().increment(hashName, hashCol, 1);  11         } catch (Exception ex) {  12             ex.printStackTrace();  13         }  14         return 0;  15     }

定期刪除無用hash列

就上面我們通過hash來設置每天id只增初始值,hash的hkey布局用自動過期功能,因此我們需要程式碼中維護一套清除來hkey的機制,既然id是根據日期生成,我們可以就用往前推n天的方式達到清除老hkey目的:

 1     /**   2      * 刪除多少天之前的cols   3      * @param hashName   4      * @param lessDay   5      * @return   6      */   7     public Long removePrimaryByLessDay(String hashName, int lessDay) {   8         try {   9             //當前日期  10             String hashCol = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));  11             long idl = Long.valueOf(hashCol) - lessDay;  12  13             String[] removeCols = redisTemplate.opsForHash().entries(hashName).keySet().stream().  14                     map(key -> key.toString()).  15                     filter(key -> idl > Long.valueOf(key)).  //從+1開始,避免刪除當天數據  16                     toArray(String[]::new);  17  18             if (ArrayUtils.isNotEmpty(removeCols)) {  19                 return redisTemplate.opsForHash().delete(hashName, removeCols);  20             }  21         } catch (Exception ex) {  22             ex.printStackTrace();  23         }  24         return 0L;  25     }

按照日期來生成分散式id,達到id不重複的目的,這也就是分散式id(不重複),看起來簡單其實如果在高流量衝擊下,需要考慮的東西要很多,比如:什麼時候生成初始Id、在多個伺服器保證伺服器時間儘可能一樣情況下,該保留多少日期hkey等;

就上面程式碼對初始Id就做的不是很好,在業務獲取Id時候,會去檢測並創建id,這樣與redis交互就多了一次,通常可以用服務來一次性生成當前日期往後推n天的hkey,這樣就避免了在業務獲取id時候,還要去putIfAbsent一次驗證,減少了請求次數。實在不行可以使用lua腳本放在一次請求去做put和increment,你可能會用到:

1             RedisScript script = new DefaultRedisScript("");  2             redisTemplate.execute(script, Arrays.asList(""));