参考文章:https://juejin.cn/post/7007589343602671653?utm_source=gold_browser_extension
雪花算法使用一个 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 自增:存入数据库中,索引效率高
SnowFlake 算法的缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成 ID 冲突或者重复
雪花算法生成 ID 冲突存在一定的前提条件
- 服务通过集群的方式部署,其中部分机器标识位一致
- 业务存在一定的并发量,没有并发量无法触发重复问题
- 生成 ID 的时机:同一毫秒下的序列号一致
知道了 ID 重复的必要条件。如果要避免服务内产生重复的 ID,那么就需要从标识位上动文章。
Mybatis-plus 中使用的雪花算法
mybatis-plus自3.3.0开始,默认使用雪花算法+UUID(不含中划线)
但是它并没有强制让开发者配置机器号,而是通过当前物理网卡地址和jvm的进程ID自动生成。
//无参构造 开发者没有设置机器号时public Sequence() {//通过当前物理网卡地址获取datacenterIdthis.datacenterId = getDatacenterId(maxDatacenterId);//物理网卡地址+jvm进程pi获取workerIdthis.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.87InetAddress ip = InetAddress.getLocalHost();NetworkInterface network = NetworkInterface.getByInetAddress(ip);//一般不是null会进入elseif (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);}
