MybatisPlus使用idworker解決雪花算法重復(fù)
一、雪花算法datacenterId重復(fù)問題
華為云的服務(wù)器的/etc/hosts中都會生成一條 127.0.1.1 hostname的記錄 ,導(dǎo)致獲取network為null ,datacenterId 會取默認(rèn)值1,導(dǎo)致重復(fù)概率大大增加。
二、idworker 是一個基于zookeeper和snowflake算法的分布式統(tǒng)一ID生成工具
通過zookeeper自動注冊機器(最多1024臺),無需手動指定workerId和dataCenterId。
通過ZooKeeper持久順序節(jié)點特性,來配置維護節(jié)點的編號NODEID。
集群節(jié)點命名服務(wù)的基本流程是:
(1)啟動節(jié)點服務(wù),連接ZooKeeper, 檢查命名服務(wù)根節(jié)點根節(jié)點是否存在,如果不存在就創(chuàng)建系統(tǒng)根節(jié)點。
(2)在根節(jié)點下創(chuàng)建一個臨時順序節(jié)點,取回順序號做節(jié)點的NODEID。如何臨時節(jié)點太多,可以根據(jù)需要,刪除臨時節(jié)點。
由于是采用zookeeper順序節(jié)點的特性生成datacenterId和workerId,可以天然的保證datacenterId和workerId的唯一性,減少了人工維護的弊端。
三、idworker使用
1、mybatis-plus-boot-starter要升級到3.4.0以上,根據(jù)具體項目不同選擇合適的版本
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency>
2、增加idworker的1.5.0版本的依賴
<dependency> <groupId>com.imadcn.framework</groupId> <artifactId>idworker</artifactId> <version>1.5.0</version> </dependency>
3、增加IdAutoConfig.java文件
@Configurationd public class IdAutoConfig { ? ? @Value("${mybatis-plus.zookeeper.serverLists:127.0.0.1:2181}") ? ? private String zkServerLists; ? ? @Bean ? ? public IdentifierGenerator idGenerator() { ? ? ? ? return new ImadcnIdentifierGenerator(zkServerLists); ? ? } }
或者:
@Configuration @MapperScan( ? ? ? ? basePackages = "com.script.idworker.mapper", ? ? ? ? sqlSessionFactoryRef = "sqlSessionFactory") public class DataSourceConfig { ? ? @Value("${mybatis-plus.zookeeper.serverLists}") ? ? private String zkServerLists; ? ? @Bean(name = "dataSource") ? ? @Primary ? ? @ConfigurationProperties(prefix = "spring.datasource.druid") ? ? public DataSource getDataSource() { ? ? ? ? return DruidDataSourceBuilder.create().build(); ? ? } ? ? @Bean(name = "sqlSessionFactory") ? ? @Primary ? ? public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource datasource) throws Exception { ? ? ? ? MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean(); ? ? ? ? sqlSessionFactory.setDataSource(datasource); ? ? ? ? MybatisConfiguration configuration = new MybatisConfiguration(); ? ? ? ? // 駝峰轉(zhuǎn)下劃線 ? ? ? ? configuration.setMapUnderscoreToCamelCase(true); ? ? ? ? sqlSessionFactory.setConfiguration(configuration); ? ? ? ?? ? ? ? ? // 設(shè)置使用Mybatis的Snowflake算法生成id ? ? ? ? GlobalConfig globalConfig = new GlobalConfig(); ? ? ? ? globalConfig.setIdentifierGenerator(new ImadcnIdentifierGenerator(zkServerLists)); ? ? ? ? sqlSessionFactory.setGlobalConfig(globalConfig); ? ? ? ?? ? ? ? ? return sqlSessionFactory.getObject(); ? ? } }
4、可能curator版本沖突問題,idworker依賴的curator是4.x版本的,可能和dubbo依賴的curator版本沖突,可能和zookeeper 3.4.x版本不兼容
四、idworker源碼分析
1、返回SnowflakeId
Snowflake.java#nextId()
public synchronized long nextId() { ? ? long timestamp = timeGen(); ? ? // 如果上一個timestamp與新產(chǎn)生的相等,則sequence加一(0-4095循環(huán)); ? ? if (lastTimestamp == timestamp) { ? ? ? ? // 對新的timestamp,sequence從0開始 ? ? ? ? sequence = sequence + 1 & sequenceMask; ? ? ? ? // 毫秒內(nèi)序列溢出 ? ? ? ? if (sequence == 0) { ? ? ? ? ? ? // 阻塞到下一個毫秒,獲得新的時間戳 ? ? ? ? ? ? sequence = RANDOM.nextInt(100); ? ? ? ? ? ? timestamp = tilNextMillis(lastTimestamp); ? ? ? ? } ? ? } else { ? ? ? ? // 時間戳改變,毫秒內(nèi)序列重置 ? ? ? ? sequence = RANDOM.nextInt(100); ? ? } ? ? // 如果當(dāng)前時間小于上一次ID生成的時間戳,說明系統(tǒng)時鐘回退過這個時候應(yīng)當(dāng)拋出異常 ? ? if (timestamp < lastTimestamp) { ? ? ? ? String message = String.format("Clock moved backwards. Refusing to generate id for %d milliseconds.", ? ? ? ? ? ? ? ? (lastTimestamp - timestamp)); ? ? ? ? logger.error(message); ? ? ? ? throw new RuntimeException(message); ? ? } ? ? lastTimestamp = timestamp; ? ? // 移位并通過或運算拼到一起組成64位的ID ? ? // 1 + 41 + 10 + 22 ? ? // 0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000 ? ? return timestamp - epoch << timestampLeftShift | workerId << workerIdShift | sequence; }
- 1位標(biāo)識,由于long基本類型在Java中是帶符號的,最高位是符號位,正數(shù)是0,負(fù)數(shù)是1,所以id一般是正數(shù),最高位是0
- 41位時間戳(毫秒級),注意,41位時間戳不是存儲當(dāng)前時間的時間戳,而是存儲時間戳的差值(當(dāng)前時間戳 - 開始時間戳)得到的值),這里的的開始時間戳,一般是我們的id生成器開始使用的時間,由我們程序來指定的(如下下面程序epoch屬性)。41位的時間戳,可以使用69年
- 10位的數(shù)據(jù)機器位,可以部署在1024個節(jié)點,包括5位datacenterId和5位workerId,
- 12位序列,毫秒內(nèi)的計數(shù),12位的計數(shù)順序號支持每個節(jié)點每毫秒(同一機器,同一時間戳)產(chǎn)生4096個ID序號
- 加起來剛好64位,為一個Long型。
2、向zookeeper注冊workerId,返回workerId
ZookeeperWorkerRegister#register()
public long register() { ? ? InterProcessMutex lock = null; ? ? try { ? ? ? ? CuratorFramework client = (CuratorFramework) regCenter.getRawClient(); ? ? ? ? lock = new InterProcessMutex(client, nodePath.getGroupPath()); ? ? ? ? int numOfChildren = regCenter.getNumChildren(nodePath.getWorkerPath()); ? ? ? ? if (numOfChildren < MAX_WORKER_NUM) { ? ? ? ? ? ? if (!lock.acquire(MAX_LOCK_WAIT_TIME_MS, TimeUnit.MILLISECONDS)) { ? ? ? ? ? ? ? ? String message = String.format("acquire lock failed after %s ms.", MAX_LOCK_WAIT_TIME_MS); ? ? ? ? ? ? ? ? throw new TimeoutException(message); ? ? ? ? ? ? } ? ? ? ? ? ? NodeInfo localNodeInfo = getLocalNodeInfo(); ? ? ? ? ? ? List<String> children = regCenter.getChildrenKeys(nodePath.getWorkerPath()); ? ? ? ? ? ? // 有本地緩存的節(jié)點信息,同時ZK也有這條數(shù)據(jù) ? ? ? ? ? ? if (localNodeInfo != null && children.contains(String.valueOf(localNodeInfo.getWorkerId()))) { ? ? ? ? ? ? ? ? String key = getNodePathKey(nodePath, localNodeInfo.getWorkerId()); ? ? ? ? ? ? ? ? String zkNodeInfoJson = regCenter.get(key); ? ? ? ? ? ? ? ? NodeInfo zkNodeInfo = createNodeInfoFromJsonStr(zkNodeInfoJson); ? ? ? ? ? ? ? ? if (checkNodeInfo(localNodeInfo, zkNodeInfo)) { ? ? ? ? ? ? ? ? ? ? // 更新ZK節(jié)點信息,保存本地緩存,開啟定時上報任務(wù) ? ? ? ? ? ? ? ? ? ? nodePath.setWorkerId(zkNodeInfo.getWorkerId()); ? ? ? ? ? ? ? ? ? ? zkNodeInfo.setUpdateTime(new Date()); ? ? ? ? ? ? ? ? ? ? updateZookeeperNodeInfo(key, zkNodeInfo); ? ? ? ? ? ? ? ? ? ? saveLocalNodeInfo(zkNodeInfo); ? ? ? ? ? ? ? ? ? ? executeUploadNodeInfoTask(key, zkNodeInfo); ? ? ? ? ? ? ? ? ? ? return zkNodeInfo.getWorkerId(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? ? ? // 無本地信息或者緩存數(shù)據(jù)不匹配,開始向ZK申請節(jié)點機器ID ? ? ? ? ? ? for (int workerId = 0; workerId < MAX_WORKER_NUM; workerId++) { ? ? ? ? ? ? ? ? String workerIdStr = String.valueOf(workerId); ? ? ? ? ? ? ? ? if (!children.contains(workerIdStr)) { // 申請成功 ? ? ? ? ? ? ? ? ? ? NodeInfo applyNodeInfo = createNodeInfo(nodePath.getGroupName(), workerId); ? ? ? ? ? ? ? ? ? ? nodePath.setWorkerId(applyNodeInfo.getWorkerId()); ? ? ? ? ? ? ? ? ? ? // 保存ZK節(jié)點信息,保存本地緩存,開啟定時上報任務(wù) ? ? ? ? ? ? ? ? ? ? saveZookeeperNodeInfo(nodePath.getWorkerIdPath(), applyNodeInfo); ? ? ? ? ? ? ? ? ? ? saveLocalNodeInfo(applyNodeInfo); ? ? ? ? ? ? ? ? ? ? executeUploadNodeInfoTask(nodePath.getWorkerIdPath(), applyNodeInfo); ? ? ? ? ? ? ? ? ? ? return applyNodeInfo.getWorkerId(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? throw new RegException("max worker num reached. register failed"); ? ? } catch (RegException e) { ? ? ? ? throw e; ? ? } catch (Exception e) { ? ? ? ? logger.error("", e); ? ? ? ? throw new IllegalStateException(e.getMessage(), e); ? ? } finally { ? ? ? ? try { ? ? ? ? ? ? if (lock != null) { ? ? ? ? ? ? ? ? lock.release(); ? ? ? ? ? ? } ? ? ? ? } catch (Exception ignored) { ? ? ? ? ? ? logger.error("", ignored); ? ? ? ? } ? ? } }
五、idworker缺點
idworker向zookeeper注冊workerId,返回workerId后,會在本地緩存workerId,這樣就會導(dǎo)致如果同一臺機器部署了多個應(yīng)用,那么多個應(yīng)用會共享同一個本地緩存,所以仍有可能造成id重復(fù)。
到此這篇關(guān)于MybatisPlus使用idworker解決雪花算法重復(fù)的文章就介紹到這了,更多相關(guān)MybatisPlus dworker雪花算法重復(fù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
async-excel實現(xiàn)多sheet異步導(dǎo)出方法詳解
這篇文章主要介紹了async-excel實現(xiàn)多sheet異步導(dǎo)出方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12Java請求轉(zhuǎn)發(fā)和請求重定向區(qū)別詳解
這篇文章主要介紹了Java請求轉(zhuǎn)發(fā)和請求重定向區(qū)別詳解,請求轉(zhuǎn)發(fā)和請求重定向,但二者是完全不同的,所以我們今天就來盤他們的區(qū)別介紹,需要的朋友可以參考一下2022-07-07Spring?Boot?教程之創(chuàng)建項目的三種方式
這篇文章主要分享了Spring?Boot?教程之創(chuàng)建項目的三種方式,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-05-05SpringCloud Nacos作為配置中心超詳細(xì)講解
這篇文章主要介紹了Springcloud中的Nacos作為配置中心,本文以用戶微服務(wù)為例,進行統(tǒng)一的配置,結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12三分鐘讀懂mybatis中resultMap和resultType區(qū)別
這篇文章主要給大家介紹了mybatis中resultMap和resultType區(qū)別的相關(guān)資料,resultType和resultMap都是mybatis進行數(shù)據(jù)庫連接操作處理返回結(jié)果的,需要的朋友可以參考下2023-07-07mybatis報錯?resultMapException的解決
這篇文章主要介紹了mybatis報錯?resultMapException的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01