MybatisPlus使用idworker解决雪花算法重复
作者:Winner002 发布时间:2023-02-28 17:19:09
一、雪花算法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重复。
来源:https://blog.csdn.net/GoGleTech/article/details/128713536


猜你喜欢
- 经过很多查看在巨人的肩膀上写完这篇博客,如有雷同纯属巧合,虽然自己也查了些文章才总结的,但是站在巨人肩膀上不敢搞原创!学习使用一些插件,可以
- AlarmManager通常用来开发手机闹钟,并且它是一个全局定时器,可在指定时间或指定周期启动其他组件(包括Activity,Servic
- 本文实例为大家分享了Android Webview使用小结,供大家参考,具体内容如下#采用重载URL的方式实现Java与Js交互在Andro
- 1. 问题描述:自己修改了下 ${M2_HOME}/conf/settings.xml中的本地repository地址,但是重新执行mvn的
- Eureka什么是服务治理为什么需要服务治理?  服务治理是主要针对分布式服务框架的微服务,处理服务调用
- java中初始化MediaRecorder实现代码:private boolean initializeVideo() { &
- 最近做了个自定义键盘,但面对不同分辨率的机型其中数字键盘不能根据界面大小自已铺满,但又不能每种机型都做一套吧,所以要做成自适应,那这里主讲思
- Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Conditi
- 很多朋友问小编springboot项目中怎么集成Swagger呢?swagger世界上最好的api管理工具前言我们为什么要使用api管理工具
- rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统。它遵循Mozilla Public License开源协议,采用
- 项目结构项目路径可以自己定义,只要路径映射正确就可以pom.xml <properties> <spring.versio
- 如下所示:JSONArray jsonArray1 = jsonObject.getJSONArray("result"
- 一、项目简述功能: 前后用户的登录注册,婚纱照片分类,查看,摄影师预 订,后台订单管理,图片管理等等。二、项目运行环境配置: Jdk1.8
- 在上篇文章给大家介绍了使用XSD校验Mybatis的SqlMapper配置文件的方法(1),需要的朋友可以参考下。编写好XSD文件,然后来看
- 1、添加一个App.config配置文件。2、配置服务http://Lenovo-PC:80/EvisaWS/WharfService?ws
- 简介在上一篇文章中,我们列举了flutter中的所有layout类,并且详细介绍了两个非常常用的layout:Row和Column。掌握了上
- 前言:最近终于用上了高性能的测试机(54C96G * 3),相较之前的单机性能提升了三倍,数量提升了三倍,更关键的宽带提单机升了30倍不止,
- #简易版1、客户发送请求经过 DisPatcherServlet 核心过滤器2、DisPatcherServlet 核心控制器在去找一个或多
- 很久以来都想研究一下利用java操作Excel的方法,今天没事,就稍微了解了一下,特总结一下。利用java操作Excel,有个开源的东东-j
- 一、背景在通过Runnable接口创建线程时,启动线程需要借助Thread类,这里就涉及到了静态代理模式。二、实例以歌手演出为例,在演出的这