面试官:讲讲雪花算法,越详细越好( 二 )

  • 3.如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务 。
  • 缓存workerID,减少第三方组件的依赖
  • 由于强依赖时钟,对时间的要求比较敏感,在机器工作时NTP同步也会造成秒级别的回退,建议可以直接关闭NTP同步 。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上即可 。或者做一层重试,然后上报报警系统,更或者是发现有时钟回拨之后自动摘除本身节点并报警
  • 代码展示public class SnowFlake {// 数据中心(机房) idprivate long datacenterId;// 机器IDprivate long workerId;// 同一时间的序列private long sequence;public SnowFlake(long workerId, long datacenterId) {this(workerId, datacenterId, 0);}public SnowFlake(long workerId, long datacenterId, long sequence) {// 合法判断if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);this.workerId = workerId;this.datacenterId = datacenterId;this.sequence = sequence;}// 开始时间戳(2021-10-16 22:03:32)private long twepoch = 1634393012000L;// 机房号,的ID所占的位数 5个bit 最大:11111(2进制)--> 31(10进制)private long datacenterIdBits = 5L;// 机器ID所占的位数 5个bit 最大:11111(2进制)--> 31(10进制)private long workerIdBits = 5L;// 5 bit最多只能有31个数字,就是说机器id最多只能是32以内private long maxWorkerId = -1L ^ (-1L << workerIdBits);// 5 bit最多只能有31个数字,机房id最多只能是32以内private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);// 同一时间的序列所占的位数 12个bit 111111111111 = 4095最多就是同一毫秒生成4096个private long sequenceBits = 12L;// workerId的偏移量private long workerIdShift = sequenceBits;// datacenterId的偏移量private long datacenterIdShift = sequenceBits + workerIdBits;// timestampLeft的偏移量private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;// 序列号掩码 4095 (0b111111111111=0xfff=4095)// 用于序号的与运算,保证序号最大值在0-4095之间private long sequenceMask = -1L ^ (-1L << sequenceBits);// 最近一次时间戳private long lastTimestamp = -1L;// 获取机器IDpublic long getWorkerId() {return workerId;}// 获取机房IDpublic long getDatacenterId() {return datacenterId;}// 获取最新一次获取的时间戳public long getLastTimestamp() {return lastTimestamp;}// 获取下一个随机的IDpublic synchronized long nextId() {// 获取当前时间戳,单位毫秒long timestamp = timeGen();if (timestamp < lastTimestamp) {System.err.printf("clock is moving backwards.Rejecting requests until %d.", lastTimestamp);throw new RuntimeException(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds",lastTimestamp - timestamp));}// 去重if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;// sequence序列大于4095if (sequence == 0) {// 调用到下一个时间戳的方法timestamp = tilNextMillis(lastTimestamp);}} else {// 如果是当前时间的第一次获取,那么就置为0sequence = 0;}// 记录上一次的时间戳lastTimestamp = timestamp;// 偏移计算return ((timestamp - twepoch) << timestampLeftShift) |(datacenterId << datacenterIdShift) |(workerId << workerIdShift) |sequence;}private long tilNextMillis(long lastTimestamp) {// 获取最新时间戳long timestamp = timeGen();// 如果发现最新的时间戳小于或者等于序列号已经超4095的那个时间戳while (timestamp <= lastTimestamp) {// 不符合则继续timestamp = timeGen();}return timestamp;}private long timeGen() {return System.currentTimeMillis();}public static void main(String[] args) {SnowFlake worker = new SnowFlake(1, 1);long timer = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {worker.nextId();}System.out.println(System.currentTimeMillis());System.out.println(System.currentTimeMillis() - timer);}}问题分析1. 第一位为什么不使用?在计算机的表示中,第一位是符号位,0表示整数,第一位如果是1则表示负数,我们用的ID默认就是正数,所以默认就是0,那么这一位默认就没有意义 。
    2.机器位怎么用?机器位或者机房位,一共10 bit,如果全部表示机器,那么可以表示1024台机器,如果拆分,5 bit 表示机房,5bit表示机房里面的机器,那么可以有32个机房,每个机房可以用32台机器 。


    推荐阅读