您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“分布式業務系統中全局ID生成策略的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“分布式業務系統中全局ID生成策略的示例分析”這篇文章吧。
在實際的開發中,幾乎所有的業務場景產生的數據,都需要一個唯一ID作為核心標識,用來流程化管理。比如常見的:
訂單:order-id,查訂單詳情,物流狀態等;
支付:pay-id,支付狀態,基于ID事務管理;
如何生成唯一標識,在普通場景下,一般的方法就可以解決,例如:
import java.util.UUID; public class UuidUtil { public static String getUUid() { UUID uuid = UUID.randomUUID(); return String.valueOf(uuid).replace("-",""); } }
這個方法可以解決絕大部分唯一ID需求的場景業務,但是網上各種UUID重復場景的描述帖,說的好像該API不好用。
絮叨一句
:說一個真實使用的業務場景,大概是半年近3000萬的數據流水,用的就是UUID的API,暫時未捕捉到ID重復的問題,僅供參考。
Twitter公司開源的分布式ID生成算法策略,生成的ID遵循時間的順序。
1為位標識,始終為0,不可用;
41位時間截,存儲時間截的差值(當前時間截-開始時間截);
10位的機器標識,10位的長度最多支持部署1024個節點;
12位序列,毫秒內的計數,12位的計數順序號支持每個節點每毫秒產生4096個ID序號;
SnowFlake的優點是,整體上按照時間自增排序,并且整個分布式系統內不會產生ID碰撞(由數據中心ID和機器ID作區分),并且效率較高。
工具類中很多可以自定義的,比如起始時間,機器ID配置等。
/** * 雪花算法ID生成 */ public class SnowIdWorkerUtil { // 開始時間截 (2020-01-02) private final long timeToCut = 1577894400000L; // 機器ID所占的位數 private final long workerIdBits = 2L; // 數據標識ID所占的位數 private final long dataCenterIdBits = 8L; // 支持的最大機器ID,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數) private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 支持的最大數據標識ID,結果是31 private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits); // 序列在ID中占的位數 private final long sequenceBits = 12L; // 機器ID向左移12位 private final long workerIdShift = sequenceBits; // 數據標識ID向左移17位(12+5) private final long dataCenterIdShift = sequenceBits + workerIdBits; // 時間截向左移22位(5+5+12) private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits; // 生成序列的掩碼 private final long sequenceMask = -1L ^ (-1L << sequenceBits); // 工作機器ID(0~31) private long workerId; // 數據中心ID(0~31) private long dataCenterId; // 毫秒內序列(0~4095) private long sequence = 0L; // 上次生成ID的時間截 private long lastTimestamp = -1L; /** * 構造函數 * @param workerId 工作ID (0~31) * @param dataCenterId 數據中心ID (0~31) */ public SnowIdWorkerUtil (long workerId, long dataCenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException("workerId 不符合條件"); } if (dataCenterId > maxDataCenterId || dataCenterId < 0) { throw new IllegalArgumentException("dataCenterId 不符合條件"); } this.workerId = workerId; this.dataCenterId = dataCenterId; } public synchronized String nextIdVar(){ return String.valueOf(nextId()); } /** * 線程安全,獲得下一個ID */ private synchronized long nextId() { long timestamp = timeGen(); // 如果當前時間小于上一次ID生成的時間戳,拋出異常 if (timestamp < lastTimestamp) { throw new RuntimeException(String.format( "時間順序異常,時間差(上次時間-現在)=%d", lastTimestamp - timestamp)); } // 如果是同一時間生成的,則進行毫秒內序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; //毫秒內序列溢出 if (sequence == 0) { //阻塞到下一個毫秒,獲得新的時間戳 timestamp = tilNextMillis(lastTimestamp); } } else { // 時間戳改變,毫秒內序列重置 sequence = 0L; } // 上次生成ID的時間截 lastTimestamp = timestamp; // 移位并通過或運算拼到一起組成64位的ID return ((timestamp - timeToCut) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence; } /** * 阻塞,獲得新的時間戳 */ private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回當前時間節點 */ private long timeGen() { return System.currentTimeMillis(); } public static void main(String[] args) { // 參數在實際業務下需要配置管理 SnowIdWorkerUtil idWorker = new SnowIdWorkerUtil(1, 1); for (int i = 0; i < 100; i++) { String id = idWorker.nextIdVar(); System.out.println(id+" "+id.length()+"位"); } } }
還有一種常見的實現思路,基于數據庫的自增主鍵ID,不過基于這個原理,卻有各種不同的實現策略。
簡單表結構:
CREATE TABLE `du_temp_id` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id', `create_time` datetime DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='主鍵ID臨時表';
這種模式的原理比較單調,向臨時表寫入一條記錄,借助MySQL生成的唯一主鍵ID,然后拿出來稍微處理一下,作為各種業務場景的唯一ID使用。
@Service public class TempIdServiceImpl implements TempIdService { @Resource private TempIdMapper tempIdMapper ; @Override public List<String> getIdList() { List<String> idList = new ArrayList<>() ; TempIdEntity tempIdEntity = new TempIdEntity (); tempIdEntity.setCreateTime(new Date()); for (int i = 0 ; i < 10 ; i++){ tempIdMapper.insert(tempIdEntity); idList.add(UuidUtil.getNoId(8,Long.parseLong(tempIdEntity.getId().toString()))) ; } return idList ; } }
問題點:如果作為ID生成的臨時表所在的MySQL服務宕掉,那可能會影響整個業務流程,造成雪崩效應。
單服務如果不能安穩的支撐業務需求,很自然集群模式就該上場了。提供多臺MySQL服務[A,B,C],處理策略也不止一種:
庫設置主鍵自增策略
例如A庫[1,4,7],B庫[2,5,8],C庫[3,6,9],基于不同自增規則,生成統一的自增唯一標識。
生成ID做分庫標識
這種先把ID生成,然后不同的數據庫生成的ID給一個不同的標識,例如UIDA,UIDB,UIDC。
@Service public class TempIdServiceImpl implements TempIdService { @Resource private TempIdMapper tempIdMapper ; @Override public List<String> getRouteIdList() { List<String> idList = new ArrayList<>() ; TempIdEntity tempIdEntity = new TempIdEntity (); tempIdEntity.setCreateTime(new Date()); for (int i = 0 ; i < 2 ; i++){ tempIdMapper.insertA(tempIdEntity); idList.add(UuidUtil.getRouteId("UID-A",10, Long.parseLong(tempIdEntity.getId().toString()))) ; tempIdMapper.insertB(tempIdEntity); idList.add(UuidUtil.getRouteId("UID-B",10, Long.parseLong(tempIdEntity.getId().toString()))) ; tempIdMapper.insertC(tempIdEntity); idList.add(UuidUtil.getRouteId("UID-C",10, Long.parseLong(tempIdEntity.getId().toString()))) ; } return idList ; } }
結果樣例:
UID-A00001,UID-B00001,UID-C00001
UID-A00002,UID-B00002,UID-C00002
從數據獲取的ID基本是一個自增的整數序列,可以提供一個格式美化工具方法。
public class UuidUtil { private static final String ZERO = "00000000000"; private static final String PREFIX = "UID"; public static String getNoId(int length,Long id){ String idVar = String.valueOf(id) ; if (idVar.length()>length){ return PREFIX+idVar ; } else { int gapLen = length-idVar.length()-PREFIX.length() ; return PREFIX+ZERO.substring(0,gapLen)+idVar ; } } public static String getRouteId(String route,Integer length,Long id){ String idVar = String.valueOf(id) ; if (idVar.length()>length){ return route+idVar ; } else { int gapLen = length-idVar.length()-route.length() ; return route+ZERO.substring(0,gapLen)+idVar ; } } }
基于不同的策略,把ID格式為統一的位數。
如果在高并發的業務場景下,實時基于MySQL去生成唯一ID容易產生性能瓶頸,當然其他方法也可能產生這個問題。可以在系統空閑時間批量生成一批,放入緩存中,在使用的時候直接從緩存層取出即可。
以上是“分布式業務系統中全局ID生成策略的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。