目前项目中所有记录的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如果出现故障, 就会出现灾难级事故.
top -Hp java进程ID 这个是以cpu使用高低对线程进行排序
因为这次的问题不是cpu飚高,而是业务跑不下去导致的,所以重点在于查看时间过长的线程,举个粟子: 9335最高,以此为切入点
答案:
针对全局唯一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类中的机器标识和数据中心标识需要根据项目实际需求进行配置。