文章目录
1、雪花算法原理
雪花算法使用一个 64 bit 的 long 型的数字作为全局唯一 id。这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。
- 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
- 41bit-时间戳,用来记录时间戳,毫秒级。
- 10bit-工作机器id,用来记录工作机器id。
- 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。
SnowFlake可以保证:
- 所有生成的id按时间趋势递增
- 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
如上大概了解了雪花算法的原理,而且也知道机器号对于雪花算法的重要性。如果机器号一样,可能会出现id重复的情况。
mybatis-plus自3.3.0开始,默认使用雪花算法+UUID(不含中划线),但是它并没有强制让开发者配置机器号。这一点很是疑惑,这样可能会让不了解雪花算法的人埋下了一个坑。
但是这么强大的一个框架难道真的没有做优化吗?带着问题,查看了下mybatis-plus雪花算法源码com.baomidou.mybatisplus.core.toolkit.Sequence
。最终发现在没有设置机器号的情况下,会通过当前物理网卡地址和jvm的进程ID自动生成。这真的是一个较好的解决方案。一般在一个集群中,MAC+JVM进程PID
一样的几率非常小。
2、自动生成唯一机器号源码
核心代码。有两个构造方法,一个无参构造,一个有参构造。
public Sequence() {
//通过当前物理网卡地址获取datacenterId
this.datacenterId = getDatacenterId(maxDatacenterId);
//物理网卡地址+jvm进程pi获取workerId
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* 有参构造器
*
* @param workerId 工作机器 ID
* @param datacenterId 序列号
*/
public Sequence(long workerId, long datacenterId) {
Assert.isFalse(workerId > maxWorkerId || workerId < 0,
String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
Assert.isFalse(datacenterId > maxDatacenterId || datacenterId < 0,
String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
this.workerId = workerId;
this.datacenterId = datacenterId;
}
- 无参构造 开发者没有设置机器号时
- 有参构造 开发者自行设置机器号
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
//获取本机(或者服务器ip地址)
//DESKTOP-123SDAD/192.168.1.87
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
//一般不是null会进入else
if (network == null) {
id = 1L;
} else {
//获取物理网卡地址
byte[] mac = network.getHardwareAddress();
if (null != mac) {
id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
}
} catch (Exception e) {
logger.warn(" getDatacenterId: " + e.getMessage());
}
return id;
}
/**
* 获取 maxWorkerId
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuilder mpid = new StringBuilder();
mpid.append(datacenterId);
//获取jvm进程信息
String name = ManagementFactory.getRuntimeMXBean().getName();
if (StringUtils.isNotBlank(name)) {
/*
* 获取进程PID
*/
mpid.append(name.split(StringPool.AT)[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}