多少英雄好漢都倒在了生成ID這條路上(activiti6)

最後Q淚滴發表於2020-12-14

  又有好多天沒有寫部落格了,剛換工作比較忙,總感覺不寫點部落格就沒有進步。這篇標題主要是最近工作用到了activiti,使用過程中發現了一個問題,本著發現問題首先百度,百度講不清楚就自己研究,自己研究出來了就部落格分享,讓後人少走彎路的理念,所以寫了這篇部落格!

  首先從activiti6.0的預設ID生成說起:activiti6有個表如下

  

 

由百度知道,activiti中的ID生成是由DbIdGenerator這個類完成的,如下

 

 

 點進入GetNextIdBlockCmd這個類中

 

 

 這裡有點頭大了,每次取一塊ID,結果沒有看到啥時候更新那個next.dbid,傻子都不知道只知道取,不更新絕對會有問題,所以必然有地方更新,思路如下

1、想到這東西是用mybatis,然後我們找一下那個sql的xml:

 

 

 在scope下全域性搜尋updateProperty

 

 

 真的是懷疑人生了,根本找不到哪裡呼叫了這個更新的sql

2、這個Cmd裡先查出資料,然後setValue方法改一下值,彷彿等著後面有邏輯幫他呼叫一下那個更新方法一樣,根據經驗攔截器,AOP這些是可以做到這一點的,如果用到了這種通用處理方式,確實不大可能直接寫死呼叫updateProperty方法,所以updateProperty這個sql的Id沒法直接查到,這時候最先想到的就是拼接,update + Property這個類名,所以可以單獨全域性搜尋"update",如下

 

 

 使用快捷鍵查詢哪裡用到這裡了

 

 

 

 

 每個都點進去看一下就知道這操作有多騷了,

 

 

再次回到ID生成那個類中

 

 

  先描述一下這個ID生成作者的思想,他是想每次獲取id時一次獲取一整塊ID,並且通過一個攔截器取更新最大ID值到next.dbid的資料庫表的欄位中,每次取一個id然後nextId自增1,當取到超過整塊ID的最大值時lastId < nextId,然後再次通過查資料庫獲取一整塊ID,一直重複這個過程。當然每次重啟其實都會重新獲取一塊ID的,因為lastId的預設值時-1,nextId預設值時0,重啟必然就會滿足這個條件。  在如此之長的騷操作面前,這兩個小小的synchronized有用嗎?下面以兩個執行緒的訪問時序圖來揭示答案

 然後所以網上都說activiti的預設ID策略併發會有問題,但是沒有說清楚原因。在我看來這種程式碼要是放在我們自己的程式中就是bug,放在開源的軟體中,就被美化成了預設ID策略不支援高併發,何止是不支援高併發啊,感覺稍微一點併發就可能莫名其妙中招啊。然後就有人建議用UUID了,通過檢視實現類可以看到activiti就兩種ID策略

 

 

 說到這,該點題了,不然感覺成零分作文了,以我這麼多年開發經驗來看接觸到的ID生成策略如下

1、資料庫自增

很明顯的弊端就是,不支援高併發了

2、ID批量預取

這裡的activiti6的預設ID生成規則就是ID批量預取了,其實這個是沒有處理好,要是取得一個ID塊後立馬在同一個synchronized塊裡更新一下那個next.dbid也不會死的這麼難看了,不過就算是按照這種方式實現了,也難免出現一些問題,浪費ID、那一塊ID在使用完得那一刻多個現場都會同時去訪問資料庫,這時候得併發其實也不會很理想

3、雪花演算法

很多人推崇得方法,用起來簡單,出問題之後想死,典型場景就是伺服器因為各種原因,時間倒退了一點點(專業點:時鐘回撥),很大可能出現ID衝突,畢竟演算法裡強依賴當前時間

4、各種花裡胡哨的ID策略

大多都是ID批量預取的變種,比如搞個執行緒在後臺一直生成一批批得ID,再比如按照業務分塊,每個業務單獨使用一塊塊ID,其實都有自己的問題,最主要是實現得很複雜

4、UUID

這是一種已經放棄抵抗,認命的最終解決方案,缺點也很明顯,懂一點索引的應該知道,要想索引效率高,少走IO,要儘量保證ID短小和有順序,一個UUID就32位,而且還沒有順序,你甚至可以感覺到那索引樹又要長高不少,那IO次數又要多多少了。

  ID生成真心是一個老大難問題,就像1+1=2都很難證明一樣,往往看似簡單的問題,其實很複雜,要不然怎麼activiti這麼牛B的程式碼都會栽倒這裡了。再說一下,我看網上好多人問怎麼實現自定義的activiti的ID生成器,難道看了上面的實現類,都不知道怎麼自定義嗎,你自己實現一下IdGenerator,然後重寫唯一的方法String getNextId()都做不到嗎?還是打個樣吧

package com.rd.activiti.gen;

import org.activiti.engine.impl.cfg.IdGenerator;

import java.util.concurrent.atomic.AtomicLong;

/**
 * @author: rongdi
 * @date: 2020-12-14 22:49
 */
public class MyGen implements IdGenerator {

    private AtomicLong aLong = new AtomicLong(100);

    @Override
    public synchronized String getNextId() {
        return String.valueOf(aLong.incrementAndGet());
    }
}
<property name="idGenerator">
     <bean class="com.rd.activiti.gen.MyGen" />
</property>

  最後總結下:ID這個老大難問題,只能是具體業務具體分析了,往往最終的實現是和自己業務特性強繫結或者是在效能和滿足業務之間做一定的妥協之後的產物。

 

 

   

相關文章