参考文章: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 作为序列号。
image.png

1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
41bit-时间戳,用来记录时间戳,毫秒级。
10bit-工作机器id,用来记录工作机器id。
12bit-序列号,序列号,用来记录同毫秒内产生的不同id。即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

SnowFlake 算法的优点:

  1. 高性能高可用:生成时不依赖于数据库,完全在内存中生成
  2. 高吞吐:每秒钟能生成数百万的自增 ID
  3. ID 自增:存入数据库中,索引效率高

SnowFlake 算法的缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成 ID 冲突或者重复

雪花算法生成 ID 冲突存在一定的前提条件

  1. 服务通过集群的方式部署,其中部分机器标识位一致
  2. 业务存在一定的并发量,没有并发量无法触发重复问题
  3. 生成 ID 的时机:同一毫秒下的序列号一致

知道了 ID 重复的必要条件。如果要避免服务内产生重复的 ID,那么就需要从标识位上动文章。

Mybatis-plus 中使用的雪花算法

mybatis-plus自3.3.0开始,默认使用雪花算法+UUID(不含中划线)
但是它并没有强制让开发者配置机器号,而是通过当前物理网卡地址和jvm的进程ID自动生成。

  1. //无参构造 开发者没有设置机器号时
  2. public Sequence() {
  3. //通过当前物理网卡地址获取datacenterId
  4. this.datacenterId = getDatacenterId(maxDatacenterId);
  5. //物理网卡地址+jvm进程pi获取workerId
  6. this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
  7. }
  8. /**
  9. * 有参构造器
  10. * 有参构造 开发者自行设置机器号
  11. * @param workerId 工作机器 ID
  12. * @param datacenterId 序列号
  13. */
  14. public Sequence(long workerId, long datacenterId) {
  15. Assert.isFalse(workerId > maxWorkerId || workerId < 0,
  16. String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
  17. Assert.isFalse(datacenterId > maxDatacenterId || datacenterId < 0,
  18. String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
  19. this.workerId = workerId;
  20. this.datacenterId = datacenterId;
  21. }
  1. protected static long getDatacenterId(long maxDatacenterId) {
  2. long id = 0L;
  3. try {
  4. //获取本机(或者服务器ip地址)
  5. //DESKTOP-123SDAD/192.168.1.87
  6. InetAddress ip = InetAddress.getLocalHost();
  7. NetworkInterface network = NetworkInterface.getByInetAddress(ip);
  8. //一般不是null会进入else
  9. if (network == null) {
  10. id = 1L;
  11. } else {
  12. //获取物理网卡地址
  13. byte[] mac = network.getHardwareAddress();
  14. if (null != mac) {
  15. id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
  16. id = id % (maxDatacenterId + 1);
  17. }
  18. }
  19. } catch (Exception e) {
  20. logger.warn(" getDatacenterId: " + e.getMessage());
  21. }
  22. return id;
  23. }
  24. /**
  25. * 获取 maxWorkerId
  26. */
  27. protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
  28. StringBuilder mpid = new StringBuilder();
  29. mpid.append(datacenterId);
  30. //获取jvm进程信息
  31. String name = ManagementFactory.getRuntimeMXBean().getName();
  32. if (StringUtils.isNotBlank(name)) {
  33. /*
  34. * 获取进程PID
  35. */
  36. mpid.append(name.split(StringPool.AT)[0]);
  37. }
  38. /*
  39. * MAC + PID 的 hashcode 获取16个低位
  40. */
  41. return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
  42. }