MybatisPlus使用idworker解决雪花算法重复
作者:Winner002
一、雪花算法datacenterId重复问题
华为云的服务器的/etc/hosts中都会生成一条 127.0.1.1 hostname的记录 ,导致获取network为null ,datacenterId 会取默认值1,导致重复概率大大增加。
二、idworker 是一个基于zookeeper和snowflake算法的分布式统一ID生成工具
通过zookeeper自动注册机器(最多1024台),无需手动指定workerId和dataCenterId。
通过ZooKeeper持久顺序节点特性,来配置维护节点的编号NODEID。
集群节点命名服务的基本流程是:
(1)启动节点服务,连接ZooKeeper, 检查命名服务根节点根节点是否存在,如果不存在就创建系统根节点。
(2)在根节点下创建一个临时顺序节点,取回顺序号做节点的NODEID。如何临时节点太多,可以根据需要,删除临时节点。
由于是采用zookeeper顺序节点的特性生成datacenterId和workerId,可以天然的保证datacenterId和workerId的唯一性,减少了人工维护的弊端。
三、idworker使用
1、mybatis-plus-boot-starter要升级到3.4.0以上,根据具体项目不同选择合适的版本
<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(); // 驼峰转下划线 configuration.setMapUnderscoreToCamelCase(true); sqlSessionFactory.setConfiguration(configuration); // 设置使用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与新产生的相等,则sequence加一(0-4095循环); if (lastTimestamp == timestamp) { // 对新的timestamp,sequence从0开始 sequence = sequence + 1 & sequenceMask; // 毫秒内序列溢出 if (sequence == 0) { // 阻塞到下一个毫秒,获得新的时间戳 sequence = RANDOM.nextInt(100); timestamp = tilNextMillis(lastTimestamp); } } else { // 时间戳改变,毫秒内序列重置 sequence = RANDOM.nextInt(100); } // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 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位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
- 41位时间戳(毫秒级),注意,41位时间戳不是存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 开始时间戳)得到的值),这里的的开始时间戳,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序epoch属性)。41位的时间戳,可以使用69年
- 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId,
- 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间戳)产生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()); // 有本地缓存的节点信息,同时ZK也有这条数据 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节点信息,保存本地缓存,开启定时上报任务 nodePath.setWorkerId(zkNodeInfo.getWorkerId()); zkNodeInfo.setUpdateTime(new Date()); updateZookeeperNodeInfo(key, zkNodeInfo); saveLocalNodeInfo(zkNodeInfo); executeUploadNodeInfoTask(key, zkNodeInfo); return zkNodeInfo.getWorkerId(); } } // 无本地信息或者缓存数据不匹配,开始向ZK申请节点机器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节点信息,保存本地缓存,开启定时上报任务 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,这样就会导致如果同一台机器部署了多个应用,那么多个应用会共享同一个本地缓存,所以仍有可能造成id重复。
到此这篇关于MybatisPlus使用idworker解决雪花算法重复的文章就介绍到这了,更多相关MybatisPlus dworker雪花算法重复内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!