注意在使用hutool工具包中,雪花算法生成分布式ID时,可能会使用不当,出现重复问题。
线上一共三台实例,而数据库表的数量已经接近八百万行,系统有一个功能,需要每次批量插入1000行的数据,查看日志请求,发现当时同一秒要处理多名用户的任务,导致用户无法批量插入出现异常。
问题所在:
//workerId : 终端ID
//dataCenterId: 数据中心ID
Snowflake SNOWFLAKE = IdUtil.getSnowflake(workerId,dataCenterId)
原因:发现当时三台实例中,有两台的workerId和dataCenterId都是一样。这个问题非常严重,先看一下源码中getSnowflake方法的具体实现。
/**
* 获取单例的Twitter的Snowflake 算法生成器对象<br>
* 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
*
* <p>
* snowflake的结构如下(每部分用-分开):<br>
*
* <pre>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
* </pre>
* <p>
* 第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>
* 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)<br>
* 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
*
* <p>
* 参考:http://www.cnblogs.com/relucent/p/4955340.html
*
* @param workerId 终端ID
* @param datacenterId 数据中心ID
* @return {@link Snowflake}
* @since 4.5.9
*/
public static Snowflake getSnowflake(long workerId, long datacenterId) {
return Singleton.get(Snowflake.class, workerId, datacenterId);
}
private static long getDataCenterId(long maxDataCenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
if (null != mac) {
//bug在这里,封装时候取得第一位和第二位的mac地址
id = ((0x000000FF & (long) mac[0]) | (0x0000FF00 & (((long) mac[1]) << 8))) >> 6;
id = id % (maxDataCenterId + 1);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return id;
}
看一下getDataCenterId()
方法实现,取得mac地址的第一位和第二位生成的。然后查看服务器中的mac地址,发现两台事故机的mac地址前两位是一样的,而mac地址前两位是生产厂家标识,后四位字节是厂家特定序列号,无语至极,问题在封装的ID生成器。
因为如果每台实例的workerId和datacenterId一样,会发现重复的概率非常大,所以必须让每台实例中的workerId和datacenterId不一样,才能解决毫秒内产生最多的ID序列。