高并发下全局唯一ID生成策略

5 篇文章 0 订阅
5 篇文章 0 订阅

1. 利用全球唯一UUID

 缺点 :1.集群情况下小概率重复
 			2.UUID无序

2.基于数据库自增

缺点:占带宽    通过步长设置,后期宽展性差

3.基于Redis生成生成全局id

较数据库而言  效率大大提高,单线程先天性安全,但集群下需要设置步长,同数据库一样 扩展性不好,
高并发下有可能出现阻塞,

4.基于Twitter的Snowflake算法

高效,简单 ,推荐
高位随机+毫秒数+机器码(数据中心+机器id)+10位的流水号码
Snowflake 原理:
snowflake生产的ID是一个18位的long型数字,二进制结构表示如下(每部分用-分开):
0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000 - 00000 - 00000000 0000
第一位未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年,从1970-01-01 08:00:00),然后是5位datacenterId(最大支持25=32个,二进制表示从00000-11111,也即是十进制0-31),和5位workerId(最大支持25=32个,原理同datacenterId),所以datacenterId*workerId最多支持部署1024个节点,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生2^12=4096个ID序号).所有位数加起来共64位,恰好是一个Long型(转换为字符串长度为18).单台机器实例,通过时间戳保证前41位是唯一的,分布式系统多台机器实例下,通过对每个机器实例分配不同的datacenterId和workerId避免中间的10位碰撞。最后12位每毫秒从0递增生产ID,再提一次:每毫秒最多生成4096个ID,每秒可达4096000个。

package com.ls.algorithm;

/**

  • Twitter_Snowflake

  • SnowFlake的结构如下(每部分用-分开):

  • 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 -

  • 000000000000

  • 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0

  • 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)

  • 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。

  • 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69

  • 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId

  • 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号

  • 加起来刚好64位,为一个Long型。

  • SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,

  • SnowFlake每秒能够产生26万ID左右。
    */
    public class SnowflakeIdWorker {

    // Fields=============
    /** 开始时间截 (2015-01-01) */
    private final long twepoch = 1420041600000L;

    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;

    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;

    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;

    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~31) */
    private long workerId;

    /** 数据中心ID(0~31) */
    private long datacenterId;

    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;

    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;

    // Constructors=======
    /**

    • 构造函数
    • @param workerId
    •        工作ID (0~31)
      
    • @param datacenterId
    •        数据中心ID (0~31)
      

    */
    public SnowflakeIdWorker(long workerId, long datacenterId) {
    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));
    }
    this.workerId = workerId;
    this.datacenterId = datacenterId;
    }

    // Methods============
    /**

    • 获得下一个ID (该方法是线程安全的)

    • @return SnowflakeId
      */
      public synchronized long nextId() {
      long timestamp = timeGen();

      // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
      if (timestamp < 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;
      // 毫秒内序列溢出
      if (sequence == 0) {
      // 阻塞到下一个毫秒,获得新的时间戳
      timestamp = tilNextMillis(lastTimestamp);
      }
      }
      // 时间戳改变,毫秒内序列重置
      else {
      sequence = 0L;
      }

      // 上次生成ID的时间截
      lastTimestamp = timestamp;

      // 移位并通过或运算拼到一起组成64位的ID
      return ((timestamp - twepoch) << timestampLeftShift) //
      | (datacenterId << datacenterIdShift) //
      | (workerId << workerIdShift) //
      | sequence;
      }

    /**

    • 阻塞到下一个毫秒,直到获得新的时间戳
    • @param lastTimestamp
    •        上次生成ID的时间截
      
    • @return 当前时间戳
      */
      protected long tilNextMillis(long lastTimestamp) {
      long timestamp = timeGen();
      while (timestamp <= lastTimestamp) {
      timestamp = timeGen();
      }
      return timestamp;
      }

    /**

    • 返回以毫秒为单位的当前时间
    • @return 当前时间(毫秒)
      */
      protected long timeGen() {
      return System.currentTimeMillis();
      }

    // Test===============
    /** 测试 */
    public static void main(String[] args) {
    SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
    for (int i = 0; i < 100; i++) {
    long id = idWorker.nextId();
    String insertSQL = “insert into orderNumber value(’” + id + “’);”;
    System.out.println(insertSQL);
    }
    }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值