简单介绍下雪花算法, 以及Java版雪花算法的代码. 仅仅是一个最简单版本, 更深层次的指针回拨等. 相当于在开发过成功可以先使用. 尽量还是使用统一的分布式流水号生成系统, 保证流水号全局唯一.

雪花算法

  1. 0 0000000000 0000000000 0000000000 0000000000 0 00000 00000 000000000000

使用64位long型数字作为全局唯一id
1位 无意义 0
41位 时间戳
5位 机房id
5位 机器id
12位自增序号 表示同一时间同一机房同一机器生成的序列号

  1. 第一位为什么无意义
    二进制中 第一位代表符号位, 默认 0 表示生成的序列号为正数
  2. 41位时间戳
    41位最大能表示 2^41-1 的数字. 毫秒值 69.7年
    (2^41-1)/1000/60/60/24
    当时间大于69.7即时间戳差值大于 2199023255551, 会开始出现负值流水号
  3. 10位
    机房id+机器id 2^10 1024台机器

    1. // 但是使用中不可能每部署一台机器都改下编号, 所以我做出以下改动
    2. // 8位机器号(最大256) 2位机房号
    3. // 机器号使用IP地址后三位 机房id 默认1
    4. // 只需要确保机器的ip后三位不同即可
    5. private static final long MACHINE_BIT = 8;
    6. private static final long DATA_CENTER_BIT = 2;
    7. private static final long DATA_CENTER_ID = 1;
    8. private static long address;
    9. static {
    10. InetAddress localIp = IpUtils.getLocalIp();
    11. address = localIp.getAddress()[3] & 0xff;
    12. log.info("当前系统的 address 为: {}", address);
    13. }
  4. 12位序列号
    表示同一毫秒内生成的id 2^12-1 个正整数

SnowFlake每秒能够产生26万ID左右
优点:
生成ID时不依赖于DB,完全在内存生成,高性能高可用。
ID呈趋势递增,后续插入索引树的时候性能较好。
缺点:
依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序

SerialNumber

  1. public class SerialNumber {
  2. /**
  3. * 起始的时间戳 2018-01-01 00:00:00
  4. */
  5. private static final long START_STAMP = 1514736000000L;
  6. /**
  7. * 每一部分占用的位数
  8. * 序列号 占用位数 12 位 (同一毫秒内生成的id 2^12-1 个正整数)
  9. * 机器标识 占用位数 8 位 (一般是使用5位)
  10. * 数据中心 占用位数 2 位 (一般是使用5位)
  11. *
  12. */
  13. private static final long SEQUENCE_BIT = 12;
  14. private static final long MACHINE_BIT = 8;
  15. private static final long DATA_CENTER_BIT = 2;
  16. /**
  17. * 每一部分的最大值
  18. */
  19. private static final long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);
  20. private static final long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
  21. private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
  22. /**
  23. * 每一部分向左的位移
  24. * 机器Id左移12位 (SEQUENCE_BIT = 12)
  25. * 数据中心左移20位 (SEQUENCE_BIT + MACHINE_BIT = 12 + 8)
  26. * 时间戳左移22位 (DATA_CENTER_LEFT + DATA_CENTER_BIT = 12 + 8 + 2)
  27. *
  28. */
  29. private static final long MACHINE_LEFT = SEQUENCE_BIT;
  30. private static final long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
  31. private static final long TIME_STAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
  32. /**
  33. * 数据中心 机器标识 序列号 上一次时间戳
  34. * 数据中心标识和机器标识一般是外部传入
  35. */
  36. private static final long DATA_CENTER_ID = 1;
  37. private static long address;
  38. private long sequence = 0L;
  39. private long lastStamp = -1L;
  40. private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd");
  41. static {
  42. InetAddress localIp = IpUtils.getLocalIp();
  43. address = localIp.getAddress()[3] & 0xff;
  44. log.info("当前系统的 address 为: {}", address);
  45. }
  46. /**
  47. * 产生下一个ID
  48. *
  49. * @return
  50. */
  51. private synchronized long nextId() {
  52. long currStamp = getNewStamp();
  53. if (currStamp < lastStamp) {
  54. throw new RuntimeException("Clock moved backwards. Refusing to generate id");
  55. }
  56. if (currStamp == lastStamp) {
  57. // 相同毫秒内,序列号自增 (sequence + 1) & (~(-1L << SEQUENCE_BIT))
  58. sequence = (sequence + 1) & MAX_SEQUENCE;
  59. // 同一毫秒的序列数已经达到最大
  60. if (sequence == 0L) {
  61. currStamp = getNextMill();
  62. }
  63. } else {
  64. // 不同毫秒内,序列号置为0
  65. sequence = 0L;
  66. }
  67. lastStamp = currStamp;
  68. // 时间戳部分 数据中心部分 机器标识部分 序列号部分
  69. return (currStamp - START_STAMP) << TIME_STAMP_LEFT | DATA_CENTER_ID << DATA_CENTER_LEFT
  70. | address << MACHINE_LEFT | sequence;
  71. }
  72. private long getNextMill() {
  73. long mill = getNewStamp();
  74. while (mill <= lastStamp) {
  75. mill = getNewStamp();
  76. }
  77. return mill;
  78. }
  79. private long getNewStamp() {
  80. return System.currentTimeMillis();
  81. }
  82. }

IpUtils

  1. import java.net.*;
  2. import java.util.Enumeration;
  3. /**
  4. * @author liuzhihang
  5. * @date 2019/12/19 16:03
  6. */
  7. public class IpUtils {
  8. public static InetAddress getLocalIp() {
  9. try {
  10. for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) {
  11. NetworkInterface item = e.nextElement();
  12. for (InterfaceAddress address : item.getInterfaceAddresses()) {
  13. if (item.isLoopback() || !item.isUp()) {
  14. continue;
  15. }
  16. if (address.getAddress() instanceof Inet4Address) {
  17. return address.getAddress();
  18. }
  19. }
  20. }
  21. return InetAddress.getLocalHost();
  22. } catch (SocketException | UnknownHostException e) {
  23. throw new RuntimeException(e);
  24. }
  25. }
  26. }