JAVA项目实现全局唯一ID方法

目前项目中所有记录的ID都是通过数据库中的一个字段自增加上拼接时间来形成的唯一,但随着数据的主从复制使用,为了减轻主库压力,计划让从库也参与读取数据,这样的话会存现ID不唯一问题,请问哪位有较好的方法来解决这个问题。最好是不依赖数据库的方式

目前系统使用的是mybatis,mysql,dble,因为目前系统获取之日的序列放在主库,每次创建相关信息的时候都会在主库获取序列,如果从库允许读数据的话有可能导致序列重复,因此打算让查询序列的sql只在主库查,其他sql可以在主库和从库均衡查询,目前查找文档已解决,使用dble的hint,大家可以参考以下链接
https://www.modb.pro/db/202718
https://blog.csdn.net/weixin_43464964/article/details/121653673

雪花ID了解一下

可以考虑一下 UUID,
参考《利用java生成 UUID》

雪花算法有致命缺陷(时间回溯问题), 建议使用饿了吗的全局id生成算法, 或者美团的id生成算法, 网上都有, 这个算法不是什么秘密, 都公开了.
或者使用雪花算法的升级版, 梨花算法.
准确的说其实使用数据库也是能解决的, 但是你不要1个1个去记录, 这样自然从库有压力, 你可以使用预取术. 如果你们之前使用数据库,建议还用数据,没有问题, 你按我给你说的做, 2c的数据库可以都可以支撑, 不存在任何问题.

上述方法步骤:

当一个项目/模块/业务可以申请一个唯一标识, 其在id生成服务下的数据库保存.

这个id生成项目采用预取术, 也就是提前从数据库申请一个id段,比如1-500, 启动加载, 并且把数据库的值更为最大值, 注意只更新了这一次

开发sdk, 让其他服务使用, sdk中也使用预取术, 即预先问id生成服务要id段, 比如1-500, 当sdk发现预取id只剩下一半时, 进行下一次预取, 比如:501-1000, 此时id生成服务问数据库预取501-1000, 第二次操作数据库, 发现数据库的操作频率明显下降, 并且绝大部分时间的id取值是在内存操作, 因此不存在效率问题.

再说分布式问题, 你会发现, 上述天然支持分布式, 不如id生成服务, 第一个服务已预取了1-500, 第二个服务就会自动渠到501-1000, 因此时没有任何问题的

如果你的id消耗速度异常快, 无非是预取的范围大小而已. 又因为是分布式系统, 看起来id是自增的,实际上不是, 因为是预取, a机器的段和b机器不一致, 其实也不是必然递增. 当然这个值也可以不使用自增, 我上述只是说个方向, 希望给你启迪, 我意思是数据库是没有问题的, 用了redis我感觉才有问题, 因为redis如果出现故障, 就会出现灾难级事故.

  • 这个问题的回答你可以参考下: https://ask.csdn.net/questions/824015
  • 我还给你找了一篇非常好的博客,你可以看看是否有帮助,链接:分布式全局唯一ID解决方案
  • 除此之外, 这篇博客: java线程阻塞问题排查中的 1、获取耗时较长的线程id 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • top -Hp java进程ID    这个是以cpu使用高低对线程进行排序

    因为这次的问题不是cpu飚高,而是业务跑不下去导致的,所以重点在于查看时间过长的线程,举个粟子: 9335最高,以此为切入点

  • 您还可以看一下 邱石老师的java后台+微信小程序 实现完整的点餐系统课程中的 拿到模板id并做推送操作小节, 巩固相关知识点
  • 以下回答由chatgpt基于相关博客总结生成:

    答案:

    针对全局唯一ID的问题,推荐使用Snowflake算法。 具体实现步骤如下:

    1.定义Snowflake类,定义以下私有变量:

    // 起始的时间戳 private final static long START_STMP = 1480166465631L; // 每一部分占用的位数,十进制中1个字节占8位,总共64位 / 序列号占用的位数 */ private final static long SEQUENCE_BIT = 12; / 机器标识占用的位数 / private final static long MACHINE_BIT = 5; / 数据中心占用的位数 / private final static long DATACENTER_BIT = 5; // 每一部分的最大值,以及最大值的二进制表示 / 数据中心最大值:31 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static String BIN_MAX_DATACENTER_NUM = Long.toBinaryString(MAX_DATACENTER_NUM); / 机器标识最大值:31 / private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static String BIN_MAX_MACHINE_NUM = Long.toBinaryString(MAX_MACHINE_NUM); / 序列号最大值:4095 / private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); private final static String BIN_MAX_SEQUENCE = Long.toBinaryString(MAX_SEQUENCE); // 记录上次生成ID的时间戳 private static long lastStmp = -1L; // 序列号 private long sequence = 0L; // 机器标识 private long machineId = 0L; // 数据中心 private long datacenterId = 0L;

    2.在Snowflake类中定义构造方法,传入机器标识和数据中心标识:

    public Snowflake(long machineId, long datacenterId) { if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("机器标识不能大于31或小于0。"); } if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { throw new IllegalArgumentException("数据中心标识不能大于31或小于0。"); } this.machineId = machineId; this.datacenterId = datacenterId; }

    3.定义nextId()方法,用于生成唯一ID:

    public synchronized long nextId() { long currStmp = getNewstmp(); if (currStmp < lastStmp) { throw new RuntimeException("时钟回退异常。"); }

    if (currStmp == lastStmp) {
        // 同一毫秒内,序列号自增
        sequence = (sequence + 1) & MAX_SEQUENCE;
        if (sequence == 0L) {
            // 超过序列号最大值时,等待下一毫秒
            currStmp = getNextMill();
        }
    } else {
        // 不同毫秒内,序列号重置
        sequence = 0L;
    }
    lastStmp = currStmp;
    
    // 二进制位移合成ID
    return (currStmp - START_STMP) << (SEQUENCE_BIT + MACHINE_BIT + DATACENTER_BIT)
            | datacenterId << (SEQUENCE_BIT + MACHINE_BIT)
            | machineId << SEQUENCE_BIT
            | sequence;
    

    }

    4.定义getNewstmp()方法和getNextMill()方法,用于获取当前时间戳和下一毫秒的时间戳:

    private long getNewstmp() { return System.currentTimeMillis(); }

    private long getNextMill() { long mill = getNewstmp(); while (mill <= lastStmp) { mill = getNewstmp(); } return mill; }

    5.在项目中使用Snowflake生成唯一ID:

    Snowflake snowflake = new Snowflake(0, 0); long id = snowflake.nextId();

    其中,Snowflake类中的机器标识和数据中心标识需要根据项目实际需求进行配置。