雪花算法(snowflake) :分布式环境,生成全局唯一的订单号

snowflake方案
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
一、为什么用snowflake
数据库自增有自增ID,但是使用起来有以下几个问题:

会依赖于数据库的具体实现,比如,mysql有自增,oracle没有,得用序列,mongo似乎也没有。
自增ID是连续的,它就依赖于数据库自身的锁,所以数据库就有瓶颈。
雪花算法不依赖于数据库本身,是分布式id生成算法中比较经典的一种。整个ID的构成大概分为这么几个部分,时间戳差值,机器编码,进程编码,序列号。java的long是64位的从左向右依次介绍是:时间戳差值,在我们这里占了42位;机器编码5位;进程编码5位;序列号12位。所有的拼接用位运算拼接起来,于是就基本做到了每个进程中不会重复了。
————————————————
原文链接:https://blog.csdn.net/nsxqf/article/details/85850232

优点:
整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。

毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
可以根据自身业务特性分配bit位,非常灵活。
缺点:
强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
针对此,美团做出了改进:https://github.com/Meituan-Dianping/Leaf
————————————————
原文链接:https://blog.csdn.net/fly910905/article/details/82054196

二、代码

/**

  • id自增器(雪花算法)

  • @author renjie

  • @version 1.0.0
    */
    public class SnowFlake {
    private final static long twepoch = 12888349746579L;
    // 机器标识位数
    private final static long workerIdBits = 5L;
    // 数据中心标识位数
    private final static long datacenterIdBits = 5L;

    // 毫秒内自增位数
    private final static long sequenceBits = 12L;
    // 机器ID偏左移12位
    private final static long workerIdShift = sequenceBits;
    // 数据中心ID左移17位
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间毫秒左移22位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    //sequence掩码,确保sequnce不会超出上限
    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
    //上次时间戳
    private static long lastTimestamp = -1L;
    //序列
    private long sequence = 0L;
    //服务器ID
    private long workerId = 1L;
    private static long workerMask = -1L ^ (-1L << workerIdBits);
    //进程编码
    private long processId = 1L;
    private static long processMask = -1L ^ (-1L << datacenterIdBits);

    private static SnowFlake snowFlake = null;

    static{
    snowFlake = new SnowFlake();
    }
    public static synchronized long nextId(){
    return snowFlake.getNextId();
    }

    private SnowFlake() {
    //获取机器编码
    this.workerId=this.getMachineNum();
    //获取进程编码
    RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
    this.processId=Long.valueOf(runtimeMXBean.getName().split("@[0]).longValue();
    //避免编码超出最大值
    this.workerId=workerId & workerMask;
    this.processId=processId & processMask;
    }

    public synchronized long getNextId() {
    //获取时间戳
    long timestamp = timeGen();
    //如果时间戳小于上次时间戳则报错
    if (timestamp < lastTimestamp) {
    try {
    throw new Exception(“Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds”);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    //如果时间戳与上次时间戳相同
    if (lastTimestamp == timestamp) {
    // 当前毫秒内,则+1,与sequenceMask确保sequence不会超出上限
    sequence = (sequence + 1) & sequenceMask;
    if (sequence == 0) {
    // 当前毫秒内计数满了,则等待下一秒
    timestamp = tilNextMillis(lastTimestamp);
    }
    } else {
    sequence = 0;
    }
    lastTimestamp = timestamp;
    // ID偏移组合生成最终的ID,并返回ID
    long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
    return nextId;
    }

    /**

    • 再次获取时间戳直到获取的时间戳与现有的不同
    • @param lastTimestamp
    • @return 下一个时间戳
      */
      private long tilNextMillis(final long lastTimestamp) {
      long timestamp = this.timeGen();
      while (timestamp <= lastTimestamp) {
      timestamp = this.timeGen();
      }
      return timestamp;
      }

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

    /**

    • 获取机器编码
    • @return
      */
      private long getMachineNum(){
      long machinePiece;
      StringBuilder sb = new StringBuilder();
      Enumeration e = null;
      try {
      e = NetworkInterface.getNetworkInterfaces();
      } catch (SocketException e1) {
      e1.printStackTrace();
      }
      while (e.hasMoreElements()) {
      NetworkInterface ni = e.nextElement();
      sb.append(ni.toString());
      }
      machinePiece = sb.toString().hashCode();
      return machinePiece;
      }
      }

使用:
long id = SnowFlake.nextId();

三、解读
ID生成逻辑

我们先看最后一步:long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
  这句话什么意思呢?
  timestamp - twepoch:时间戳减去一个时间戳,获得一个差值。
  ((timestamp - twepoch) << timestampLeftShift):timestampLeftShift是22,这个操作是将这个差值向左移22位,左移空出来的会自动补0,我们就有了22位的空间了。
  后面可以看到三个|符号,与操作会把1都加进来,而我们后面的数也都在各自的位上才有1,那么|操作就把这些数合进来了。
  (processId << datacenterIdShift):进程编码左移datacenterIdShift,这个是17位,而processId最多是5位,于是刚好填满空位
  (workerId << workerIdShift):与进程编码类似,机器编码也是5位,左移12位
  sequence最大12位。

如何确保不超出位数限制
  前面的逻辑中,我们说了很多不超出位数限制啥的内容,那么,具体是怎么做到的呢?我们拿workerId举个例子:
this.workerId=workerId & workerMask;
  这是我们确保workerId不超过5位的语句,什么意思呢?不经常操作位运算真看不懂。我们先看看workerMask是啥。
  private static long workerMask= -1L ^ (-1L << workerIdBits);
  。。。什么意思呀?它先执行的是-1L << workerIdBits,workerIdBits是5。这又是什么意思呢?注意,这是位运算,long用的是补码,-1L,就是64个1,这里使用-1是为了格式化所有位数,<<是左移运算,-1L左移五位,低位补零,也就是左移空出来的会自动补0,于是就低位五位是0,其余是1。然后^这个符号,是异或,也是位运算,位上相同则为0,不通则为1,和-1做异或,则把所有的0和1颠倒了一下。这时候,我们再看,workerId & workerMask,与操作,两个位上都为1的才能唯一,否则为零,workerMask高位都是0,所以,不管workerId高位是什么,都是0,;而workerMask低位都是1,所以,不管workerId低位是什么,都会被保留,于是,我们就控制了workerId的范围。
————————————————
原文链接:https://blog.csdn.net/nsxqf/article/details/85850232

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值