實(shí)戰(zhàn):10 分鐘掌握分布式 ID 之雪花算法
實(shí)戰(zhàn):10 分鐘掌握分布式 ID 之雪花算法
一個(gè)在生產(chǎn)每天經(jīng)過1億+數(shù)據(jù)量驗(yàn)證的id生成器
背景
1.為什么要使用雪花算法生成 ID
-- 保證 id 全局唯一
-- 保證 id 自增長
-- uuid 無序且過長
雪花算法 ID 組成

1: 1位標(biāo)識(shí)部分:
--- 在 java 中由于 long 的最高位是符號(hào)位,正數(shù)是 0,負(fù)數(shù)是 1,一般生成的 ID 為正數(shù),所以為 0;
2: 41 位時(shí)間戳部分:
--- 這個(gè)是毫秒級(jí)的時(shí)間,一般實(shí)現(xiàn)上不會(huì)存儲(chǔ)當(dāng)前的時(shí)間戳,而是時(shí)間戳的差值(當(dāng)前時(shí)間-固定的開始時(shí)間),這樣可以使產(chǎn)生的 ID 從更小值開始;41 位的時(shí)間戳可以使用 69 年,(1L<< 41) / (1000L _ 60 _ 60 _ 24 _ 365) = 69 年;
3: 10 位workid:
Twitter 實(shí)現(xiàn)中使用前 5 位作為數(shù)據(jù)中心標(biāo)識(shí),后 5 位作為機(jī)器標(biāo)識(shí),可以部署 1024 個(gè)節(jié)點(diǎn)。我這里的實(shí)現(xiàn)根據(jù)服務(wù)名生產(chǎn)的,意思就是說每個(gè)服務(wù)只要不超過 1024 個(gè)節(jié)點(diǎn)就不會(huì)有問題,實(shí)際生產(chǎn)中我也沒有見過某個(gè)服務(wù)有 1024 個(gè)節(jié)點(diǎn)的。
4: 12 位序列號(hào)部分:
--- 支持同一毫秒內(nèi)同一個(gè)節(jié)點(diǎn)可以生成 4096 個(gè) ID。意思就是說某個(gè)服務(wù) 1ms 能生成 4096 個(gè) id,如果你單表的 TPS 超過 4096\*60s,那可能就會(huì)出問題了,實(shí)際生產(chǎn)這么大的 TPS 我是沒有見過的。
Zookeeper生成workid
雪花算法生成ID網(wǎng)絡(luò)上方法很多,很多重復(fù)的東西我就不贅述了,這里簡(jiǎn)明扼要的說一下ZK生成workid。
這是生成的ID的截圖。父節(jié)點(diǎn)是workid,只要有引用id生成器jar的服務(wù)都會(huì)在workid下面生成一個(gè)文件夾,以服務(wù)名命名,有多少個(gè)節(jié)點(diǎn)該節(jié)點(diǎn)下就會(huì)有多個(gè)文件,該節(jié)點(diǎn)下的id一定是不會(huì)重復(fù)的。下面我貼一下java版生成workid的核心代碼
private void buildWorkId(final String appPath) {// 檢測(cè)client是否已經(jīng)連接上if (null == client) {throw new RuntimeException("本節(jié)點(diǎn)注冊(cè)到ZK異常。");}// lockPath,用于加鎖,注意要與nodePath區(qū)分開final String lockPath = this.ROOT_NAME + "/" + this.appName;// nodePath 用于存放集群各節(jié)點(diǎn)初始路徑final String nodePath = this.ROOT_NAME + "/" + this.appName + this.NODE_NAME;// InterProcessMutex 分布式鎖(加鎖過程中l(wèi)ockPath會(huì)自動(dòng)創(chuàng)建)InterProcessLock interProcessLock = new InterProcessMutex(client, lockPath);try {if (!interProcessLock.acquire(5000, TimeUnit.MILLISECONDS)) {throw new TimeoutException("ZK分布式鎖 加鎖超時(shí),超時(shí)時(shí)間: " + 5000);}// nodePath 第一次需初始化,永久保存, 或者節(jié)點(diǎn)路徑為臨時(shí)節(jié)點(diǎn),則設(shè)置為永久節(jié)點(diǎn)if (null == client.checkExists().forPath(nodePath)) {client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(nodePath);}// 獲取nodePath下已經(jīng)創(chuàng)建的子節(jié)點(diǎn)ListchildPath = client.getChildren().forPath(nodePath); SetnodeIdSet = new LinkedHashSet<>(); if (!CollectionUtils.isEmpty(childPath)) {for (String path : childPath) {try {nodeIdSet.add(Integer.valueOf(path));} catch (Exception e) {log.warn("路徑由不合法操作創(chuàng)建,注意[" + nodePath + "]僅用于構(gòu)建workId");}}}// 遍歷所有id,構(gòu)建workId,主要是判斷可用id是否已經(jīng)被集群中其他節(jié)點(diǎn)占用for (Integer order : OrderIdSet) {if (!nodeIdSet.contains(order)) {final String currentNodePath = nodePath + "/" + order;String nodeDate = String.format("[ip:%s,hostname:%s,pid:%s]",InetAddress.getLocalHost().getHostAddress(),InetAddress.getLocalHost().getHostName(),ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);try {client.create().withMode(CreateMode.EPHEMERAL).forPath(currentNodePath, nodeDate.getBytes("UTF-8"));} catch (Exception e) {log.debug("節(jié)點(diǎn)[{}]無法創(chuàng)建,可能是已存在", currentNodePath);continue;}long pathCreateTime = client.checkExists().forPath(currentNodePath).getCtime();// 以下邏輯主要用于檢測(cè)斷開重連情況TreeCache treeCache = new TreeCache(client, currentNodePath);// 添加監(jiān)聽器treeCache.getListenable().addListener(new TreeCacheListener() {public void childEvent(CuratorFramework curatorFramework,TreeCacheEvent treeCacheEvent) throws Exception {long pathTime;try {pathTime = curatorFramework.checkExists().forPath(currentNodePath).getCtime();} catch (Exception e) {pathTime = 0;}// 如果pathTime != pathCreateTime, 那么只能一種情況:// 當(dāng)前應(yīng)用與zk失去聯(lián)系,且/nodePath/{currentNodePath}不存在或者被其它應(yīng)用占據(jù)了(表象為pathCreateTime變化)// 無論哪種情況,當(dāng)前應(yīng)用都要重新注冊(cè)節(jié)點(diǎn)if (pathCreateTime != pathTime) {log.info("從ZK斷開,再次注冊(cè)...");// 關(guān)閉之前舊的treeCachetry {treeCache.close();} catch (Exception e) {log.warn("treeCache關(guān)閉失敗");}// 再次注冊(cè)finally {buildWorkId(appPath);}}}});treeCache.start();workerId = order;log.info("基于ZK成功構(gòu)建 workId:{}", workerId);return;}}} catch (Exception e) {e.printStackTrace();log.error("獲取分布式WorkId異常", e);} finally {// 構(gòu)建成功后釋放鎖if (interProcessLock != null) {try {interProcessLock.release();} catch (Exception e) {log.warn("釋放鎖失敗");}}}}
核心代碼我已經(jīng)貼出來了,對(duì)雪花算法有一定了解的同學(xué),使用的時(shí)候在需要id生成器的地方引用就好了,還有部分非核心代碼,這個(gè)ID生成器已經(jīng)在生產(chǎn)驗(yàn)證了每天1億+的數(shù)據(jù)量,如果你真的需要可以聯(lián)系我,把代碼都給你。zookeeper其實(shí)是一個(gè)很實(shí)用的工具,還有分布式鎖的實(shí)現(xiàn)及應(yīng)用,下篇文章會(huì)給大家?guī)碛脄k生成分布式鎖。
END
喜歡請(qǐng)掃碼關(guān)注

評(píng)論
圖片
表情
