面试复盘01

124 阅读2分钟

分布式唯一ID解决方案

分布式ID需要满足以下几个条件

  1. 全局唯一,在分布式条件下,则是在所有机器上都只能是唯一存在的
  2. 在相同环境下,优先采用数字来表示,字符串作为ID存在,在数据足够大的情况下,效率相对会比较低。以mysql为例进行分析
  • mysql innodb存储数据时,是采用B+树存储数据,使用数字在当前节点满了之后,可以直接选择下一个节点存储,如果是字符串,为了顺序排列,则需要插入到特定位置,也就会出现查询的步骤
  • 数字占用空间相对更小,在数据条数相同的情况下,树的深度会更小,查询效率更高
  1. 信息安全:如果ID是连续的,则恶意用户可能会通过这个有规律的ID找到响应的ID,可能存在一定的安全问题

UUID

采用UUID生成唯一的主键,具有唯一性,但是结果太长,保存在数据库中时,可能会导致索引太长,造成性能问题

雪花算法

img

雪花算法(SnowFlake)是由64bit组成的数据

  • 符号标识占用1bit
  • 时间戳占用41bit
  • 机器ID占用10bit
  • 序列号占用12bit,这也就是说一毫秒可以产生4096(212)4096(2^{12})个序列号

生产ID的基本流程为:

  1. 首先判断当前时间戳是否等于上一次时间戳
  2. 如果时间戳不一致,则直接将序列号设置为0
  3. 否则,判断序列号的值
  4. 如果序列号值大于4096,则需要等下一毫秒生成唯一的ID
  5. 如果序列号小于4096,则直接将序列号进行+1操作
  6. 将时间戳左移22位
  7. 将机器ID左移12位

代码实现雪花算法

/**
 * @author knxhd
 * @Classname SnowFlakeShortUrl
 * @Date 2021/12/5 16:55
 */
public class SnowFlakeShortUrl {

  /**
   * 起始的时间戳
   */
  private final static long START_TIMESTAMP = 1480166465631L;

  /**
   * 位序列
   * 序列号占用的位数
   */
  private final static long SEQUENCE_BIT = 12;
  /**
   * 机器标识占用的位数
   */
  private final static long MACHINE_BIT = 5;
  /**
   * 数据中心占用的位数
   */
  private final static long DATA_CENTER_BIT = 5;

  /**
   * 每一部分的最大值
   */
  private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
  private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
  private final static long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);

  /**
   * 每一部分向左的位移
   */
  private final static long MACHINE_LEFT = SEQUENCE_BIT;
  private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
  private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;

  /**
   * 数据中心的id
   */
  private final long dataCenterId;
  /**
   * 机器id
   */
  private final long machineId;
  /**
   * 序列
   */
  private long sequence = 0L;
  /**
   * 上一次时间戳
   */
  private long lastTimeStamp = -1L;

  private long getNextMill() {
    long currTimeStamp = getNewTimeStamp();
    while (currTimeStamp <= lastTimeStamp) {
      currTimeStamp = getNewTimeStamp();
    }
    return currTimeStamp;
  }

  private long getNewTimeStamp() {
    return System.currentTimeMillis();
  }

  /**
   * 根据指定的数据中心ID和机器标志ID生成指定的序列号
   *
   * @param dataCenterId 数据中心ID
   * @param machineId    机器标志ID
   */
  public SnowFlakeShortUrl(long dataCenterId, long machineId) {
    if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
      throw new IllegalArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!");
    }
    if (machineId > MAX_MACHINE_NUM || machineId < 0) {
      throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!");
    }
    this.dataCenterId = dataCenterId;
    this.machineId = machineId;
  }

  /**
   * 产生下一个ID
   *
   * @return
   */
  public synchronized long nextId() {
    long currTimeStamp = getNewTimeStamp();
    if (currTimeStamp < lastTimeStamp) {
      throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
    }

    if (currTimeStamp == lastTimeStamp) {
      //相同毫秒内,序列号自增,采用&运算判断是否相同
      sequence = (sequence + 1) & MAX_SEQUENCE;
      //同一毫秒的序列数已经达到最大
      if (sequence == 0L) {
        currTimeStamp = getNextMill();
      }
    } else {
      //不同毫秒内,序列号置为0
      sequence = 0L;
    }

    lastTimeStamp = currTimeStamp;

    // 时间戳部分
    return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | dataCenterId << DATA_CENTER_LEFT | machineId << MACHINE_LEFT | sequence;
  }

  public static void main(String[] args) {
    SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3);

    for (int i = 0; i < (1 << 4); i++) {
      //10进制
      System.out.println(snowFlake.nextId());
    }
  }
}