MybatisPlus使用idworker解決雪花算法重復(fù)
一、雪花算法datacenterId重復(fù)問題
華為云的服務(wù)器的/etc/hosts中都會(huì)生成一條 127.0.1.1 hostname的記錄 ,導(dǎo)致獲取network為null ,datacenterId 會(huì)取默認(rèn)值1,導(dǎo)致重復(fù)概率大大增加。
二、idworker 是一個(gè)基于zookeeper和snowflake算法的分布式統(tǒng)一ID生成工具
通過zookeeper自動(dòng)注冊(cè)機(jī)器(最多1024臺(tái)),無需手動(dòng)指定workerId和dataCenterId。
通過ZooKeeper持久順序節(jié)點(diǎn)特性,來配置維護(hù)節(jié)點(diǎn)的編號(hào)NODEID。
集群節(jié)點(diǎn)命名服務(wù)的基本流程是:
(1)啟動(dòng)節(jié)點(diǎn)服務(wù),連接ZooKeeper, 檢查命名服務(wù)根節(jié)點(diǎn)根節(jié)點(diǎn)是否存在,如果不存在就創(chuàng)建系統(tǒng)根節(jié)點(diǎn)。
(2)在根節(jié)點(diǎn)下創(chuàng)建一個(gè)臨時(shí)順序節(jié)點(diǎn),取回順序號(hào)做節(jié)點(diǎn)的NODEID。如何臨時(shí)節(jié)點(diǎn)太多,可以根據(jù)需要,刪除臨時(shí)節(jié)點(diǎn)。
由于是采用zookeeper順序節(jié)點(diǎn)的特性生成datacenterId和workerId,可以天然的保證datacenterId和workerId的唯一性,減少了人工維護(hù)的弊端。
三、idworker使用
1、mybatis-plus-boot-starter要升級(jí)到3.4.0以上,根據(jù)具體項(xiàng)目不同選擇合適的版本
<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(); ? ? // 如果上一個(gè)timestamp與新產(chǎn)生的相等,則sequence加一(0-4095循環(huán)); ? ? if (lastTimestamp == timestamp) { ? ? ? ? // 對(duì)新的timestamp,sequence從0開始 ? ? ? ? sequence = sequence + 1 & sequenceMask; ? ? ? ? // 毫秒內(nèi)序列溢出 ? ? ? ? if (sequence == 0) { ? ? ? ? ? ? // 阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳 ? ? ? ? ? ? sequence = RANDOM.nextInt(100); ? ? ? ? ? ? timestamp = tilNextMillis(lastTimestamp); ? ? ? ? } ? ? } else { ? ? ? ? // 時(shí)間戳改變,毫秒內(nèi)序列重置 ? ? ? ? sequence = RANDOM.nextInt(100); ? ? } ? ? // 如果當(dāng)前時(shí)間小于上一次ID生成的時(shí)間戳,說明系統(tǒng)時(shí)鐘回退過這個(gè)時(shí)候應(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; ? ? // 移位并通過或運(yùn)算拼到一起組成64位的ID ? ? // 1 + 41 + 10 + 22 ? ? // 0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000 ? ? return timestamp - epoch << timestampLeftShift | workerId << workerIdShift | sequence; }
- 1位標(biāo)識(shí),由于long基本類型在Java中是帶符號(hào)的,最高位是符號(hào)位,正數(shù)是0,負(fù)數(shù)是1,所以id一般是正數(shù),最高位是0
- 41位時(shí)間戳(毫秒級(jí)),注意,41位時(shí)間戳不是存儲(chǔ)當(dāng)前時(shí)間的時(shí)間戳,而是存儲(chǔ)時(shí)間戳的差值(當(dāng)前時(shí)間戳 - 開始時(shí)間戳)得到的值),這里的的開始時(shí)間戳,一般是我們的id生成器開始使用的時(shí)間,由我們程序來指定的(如下下面程序epoch屬性)。41位的時(shí)間戳,可以使用69年
- 10位的數(shù)據(jù)機(jī)器位,可以部署在1024個(gè)節(jié)點(diǎn),包括5位datacenterId和5位workerId,
- 12位序列,毫秒內(nèi)的計(jì)數(shù),12位的計(jì)數(shù)順序號(hào)支持每個(gè)節(jié)點(diǎn)每毫秒(同一機(jī)器,同一時(shí)間戳)產(chǎn)生4096個(gè)ID序號(hào)
- 加起來剛好64位,為一個(gè)Long型。
2、向zookeeper注冊(cè)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é)點(diǎn)信息,同時(shí)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é)點(diǎn)信息,保存本地緩存,開啟定時(shí)上報(bào)任務(wù) ? ? ? ? ? ? ? ? ? ? nodePath.setWorkerId(zkNodeInfo.getWorkerId()); ? ? ? ? ? ? ? ? ? ? zkNodeInfo.setUpdateTime(new Date()); ? ? ? ? ? ? ? ? ? ? updateZookeeperNodeInfo(key, zkNodeInfo); ? ? ? ? ? ? ? ? ? ? saveLocalNodeInfo(zkNodeInfo); ? ? ? ? ? ? ? ? ? ? executeUploadNodeInfoTask(key, zkNodeInfo); ? ? ? ? ? ? ? ? ? ? return zkNodeInfo.getWorkerId(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? ? ? // 無本地信息或者緩存數(shù)據(jù)不匹配,開始向ZK申請(qǐng)節(jié)點(diǎn)機(jī)器ID ? ? ? ? ? ? for (int workerId = 0; workerId < MAX_WORKER_NUM; workerId++) { ? ? ? ? ? ? ? ? String workerIdStr = String.valueOf(workerId); ? ? ? ? ? ? ? ? if (!children.contains(workerIdStr)) { // 申請(qǐng)成功 ? ? ? ? ? ? ? ? ? ? NodeInfo applyNodeInfo = createNodeInfo(nodePath.getGroupName(), workerId); ? ? ? ? ? ? ? ? ? ? nodePath.setWorkerId(applyNodeInfo.getWorkerId()); ? ? ? ? ? ? ? ? ? ? // 保存ZK節(jié)點(diǎn)信息,保存本地緩存,開啟定時(shí)上報(bào)任務(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缺點(diǎn)
idworker向zookeeper注冊(cè)workerId,返回workerId后,會(huì)在本地緩存workerId,這樣就會(huì)導(dǎo)致如果同一臺(tái)機(jī)器部署了多個(gè)應(yīng)用,那么多個(gè)應(yīng)用會(huì)共享同一個(gè)本地緩存,所以仍有可能造成id重復(fù)。
到此這篇關(guān)于MybatisPlus使用idworker解決雪花算法重復(fù)的文章就介紹到這了,更多相關(guān)MybatisPlus dworker雪花算法重復(fù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
async-excel實(shí)現(xiàn)多sheet異步導(dǎo)出方法詳解
這篇文章主要介紹了async-excel實(shí)現(xiàn)多sheet異步導(dǎo)出方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12Java請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求重定向區(qū)別詳解
這篇文章主要介紹了Java請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求重定向區(qū)別詳解,請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求重定向,但二者是完全不同的,所以我們今天就來盤他們的區(qū)別介紹,需要的朋友可以參考一下2022-07-07Spring?Boot?教程之創(chuàng)建項(xiàng)目的三種方式
這篇文章主要分享了Spring?Boot?教程之創(chuàng)建項(xiàng)目的三種方式,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05spring?boot入門之誕生背景及優(yōu)勢(shì)影響
這篇文章主要為大家描述說明了介紹了spring?boot誕生的背景以及其產(chǎn)生的優(yōu)勢(shì)影響,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03SpringCloud Nacos作為配置中心超詳細(xì)講解
這篇文章主要介紹了Springcloud中的Nacos作為配置中心,本文以用戶微服務(wù)為例,進(jìn)行統(tǒng)一的配置,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12三分鐘讀懂mybatis中resultMap和resultType區(qū)別
這篇文章主要給大家介紹了mybatis中resultMap和resultType區(qū)別的相關(guān)資料,resultType和resultMap都是mybatis進(jìn)行數(shù)據(jù)庫(kù)連接操作處理返回結(jié)果的,需要的朋友可以參考下2023-07-07mybatis報(bào)錯(cuò)?resultMapException的解決
這篇文章主要介紹了mybatis報(bào)錯(cuò)?resultMapException的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01