MybatisPlus使用idworker解决雪花算法重复

 更新时间:2023年02月05日 10:07:31   作者:Winner002  
本文主要介绍了MybatisPlus使用idworker解决雪花算法重复,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、雪花算法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雪花算法重复内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JVM 堆和栈的区别

    JVM 堆和栈的区别

    本文主要介绍了JVM堆和栈的区别。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • async-excel实现多sheet异步导出方法详解

    async-excel实现多sheet异步导出方法详解

    这篇文章主要介绍了async-excel实现多sheet异步导出方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-12-12
  • Java请求转发和请求重定向区别详解

    Java请求转发和请求重定向区别详解

    这篇文章主要介绍了Java请求转发和请求重定向区别详解,请求转发和请求重定向,但二者是完全不同的,所以我们今天就来盘他们的区别介绍,需要的朋友可以参考一下
    2022-07-07
  • Spring Boot 教程之创建项目的三种方式

    Spring Boot 教程之创建项目的三种方式

    这篇文章主要分享了Spring Boot 教程之创建项目的三种方式,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-05-05
  • spring boot入门之诞生背景及优势影响

    spring boot入门之诞生背景及优势影响

    这篇文章主要为大家描述说明了介绍了spring boot诞生的背景以及其产生的优势影响,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • SpringCloud Nacos作为配置中心超详细讲解

    SpringCloud Nacos作为配置中心超详细讲解

    这篇文章主要介绍了Springcloud中的Nacos作为配置中心,本文以用户微服务为例,进行统一的配置,结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • 三分钟读懂mybatis中resultMap和resultType区别

    三分钟读懂mybatis中resultMap和resultType区别

    这篇文章主要给大家介绍了mybatis中resultMap和resultType区别的相关资料,resultType和resultMap都是mybatis进行数据库连接操作处理返回结果的,需要的朋友可以参考下
    2023-07-07
  • mybatis报错 resultMapException的解决

    mybatis报错 resultMapException的解决

    这篇文章主要介绍了mybatis报错 resultMapException的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • java中使用xls格式化xml的实例

    java中使用xls格式化xml的实例

    这篇文章主要介绍了java中调用xls格式化xml的实例的相关资料,需要的朋友可以参考下
    2017-07-07
  • Spring的@Conditional详解

    Spring的@Conditional详解

    这篇文章主要介绍了Spring的@Conditional详解,给想要注入Bean增加限制条件,只有满足限制条件才会被构造并注入到Spring的IOC容器中,通常和@Bean注解一起使用,需要的朋友可以参考下
    2024-01-01

最新评论