一般情况,实现全局唯一ID,有三种方案,分别是通过中间件方式、UUID、雪花算法。

  方案一,通过中间件方式,可以是把数据库或者redis缓存作为媒介,从中间件获取ID。这种呢,优点是可以体现全局的递增趋势(优点只能想到这个),缺点呢,倒是一大堆,比如,依赖中间件,假如中间件挂了,就不能提供服务了;依赖中间件的写入和事务,会影响效率;数据量大了的话,你还得考虑部署集群,考虑走代理。这样的话,感觉问题复杂化了

  方案二,通过UUID的方式,java.util.UUID就提供了获取UUID的方法,使用UUID来实现全局唯一ID,优点是操作简单,也能实现全局唯一的效果,缺点呢,就是不能体现全局视野的递增趋势;太长了,UUID是32位,有点浪费;最重要的,是插入的效率低,因为呢,我们使用mysql的话,一般都是B+tree的结构来存储索引,假如是数据库自带的那种主键自增,节点满了,会裂变出新的节点,新节点满了,再去裂变新的节点,这样利用率和效率都很高。而UUID是无序的,会造成中间节点的分裂,也会造成不饱和的节点,插入的效率自然就比较低下了。

  方案三,基于redis生成全局id策略,因为Redis是单线的天生保证原子性,可以使用原子性操作INCR和INCRBY来实现,注意在Redis集群情况下,同MySQL一样需要设置不同的增长步长,同时key一定要设置有效期,可以使用Redis集群来获取更高的吞吐量

  方案四,通过snowflake算法如下:

  SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图:

    【Java】分布式自增ID算法---雪花算法 (snowflake,Java版)

  

  • 1位,不用。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0
  • 41位,用来记录时间戳(毫秒)。

    • 41位可以表示$2^{41}-1$个数字,
    • 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 $2^{41}-1$,减1是因为可表示的数值范围是从0开始算的,而不是1。
    • 也就是说41位可以表示$2^{41}-1$个毫秒的值,转化成单位年则是$(2^{41}-1) / (1000 * 60 * 60 * 24 * 365) = 69$年
  • 10位,用来记录工作机器id。

    • 可以部署在$2^{10} = 1024$个节点,包括5位datacenterId5位workerId
    • 5位(bit)可以表示的最大正整数是$2^{5}-1 = 31$,即可以用0、1、2、3、....31这32个数字,来表示不同的datecenterId或workerId
  • 12位,序列号,用来记录同毫秒内产生的不同id。

    • 12位(bit)可以表示的最大正整数是$2^{12}-1 = 4095$,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号

  由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。

  SnowFlake可以保证:

  • 所有生成的id按时间趋势递增
  • 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)

  以下是Twitter官方原版的,用Scala写的:

  1 /** Copyright 2010-2012 Twitter, Inc.*/
  2 package com.twitter.service.snowflake
  3 
  4 import com.twitter.ostrich.stats.Stats
  5 import com.twitter.service.snowflake.gen._
  6 import java.util.Random
  7 import com.twitter.logging.Logger
  8 
  9 /**
 10  * An object that generates IDs.
 11  * This is broken into a separate class in case
 12  * we ever want to support multiple worker threads
 13  * per process
 14  */
 15 class IdWorker(val workerId: Long, val datacenterId: Long, private val reporter: Reporter, var sequence: Long = 0L)
 16 extends Snowflake.Iface {
 17   private[this] def genCounter(agent: String) = {
 18     Stats.incr("ids_generated")
 19     Stats.incr("ids_generated_%s".format(agent))
 20   }
 21   private[this] val exceptionCounter = Stats.getCounter("exceptions")
 22   private[this] val log = Logger.get
 23   private[this] val rand = new Random
 24 
 25   val twepoch = 1288834974657L
 26 
 27   private[this] val workerIdBits = 5L
 28   private[this] val datacenterIdBits = 5L
 29   private[this] val maxWorkerId = -1L ^ (-1L << workerIdBits)
 30   private[this] val maxDatacenterId = -1L ^ (-1L << datacenterIdBits)
 31   private[this] val sequenceBits = 12L
 32 
 33   private[this] val workerIdShift = sequenceBits
 34   private[this] val datacenterIdShift = sequenceBits + workerIdBits
 35   private[this] val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits
 36   private[this] val sequenceMask = -1L ^ (-1L << sequenceBits)
 37 
 38   private[this] var lastTimestamp = -1L
 39 
 40   // sanity check for workerId
 41   if (workerId > maxWorkerId || workerId < 0) {
 42     exceptionCounter.incr(1)
 43     throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0".format(maxWorkerId))
 44   }
 45 
 46   if (datacenterId > maxDatacenterId || datacenterId < 0) {
 47     exceptionCounter.incr(1)
 48     throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0".format(maxDatacenterId))
 49   }
 50 
 51   log.info("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
 52     timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId)
 53 
 54   def get_id(useragent: String): Long = {
 55     if (!validUseragent(useragent)) {
 56       exceptionCounter.incr(1)
 57       throw new InvalidUserAgentError
 58     }
 59 
 60     val id = nextId()
 61     genCounter(useragent)
 62 
 63     reporter.report(new AuditLogEntry(id, useragent, rand.nextLong))
 64     id
 65   }
 66 
 67   def get_worker_id(): Long = workerId
 68   def get_datacenter_id(): Long = datacenterId
 69   def get_timestamp() = System.currentTimeMillis
 70 
 71   protected[snowflake] def nextId(): Long = synchronized {
 72     var timestamp = timeGen()
 73 
 74     if (timestamp < lastTimestamp) {
 75       exceptionCounter.incr(1)
 76       log.error("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
 77       throw new InvalidSystemClock("Clock moved backwards.  Refusing to generate id for %d milliseconds".format(
 78         lastTimestamp - timestamp))
 79     }
 80 
 81     if (lastTimestamp == timestamp) {
 82       sequence = (sequence + 1) & sequenceMask
 83       if (sequence == 0) {
 84         timestamp = tilNextMillis(lastTimestamp)
 85       }
 86     } else {
 87       sequence = 0
 88     }
 89 
 90     lastTimestamp = timestamp
 91     ((timestamp - twepoch) << timestampLeftShift) |
 92       (datacenterId << datacenterIdShift) |
 93       (workerId << workerIdShift) | 
 94       sequence
 95   }
 96 
 97   protected def tilNextMillis(lastTimestamp: Long): Long = {
 98     var timestamp = timeGen()
 99     while (timestamp <= lastTimestamp) {
100       timestamp = timeGen()
101     }
102     timestamp
103   }
104 
105   protected def timeGen(): Long = System.currentTimeMillis()
106 
107   val AgentParser = """([a-zA-Z][a-zA-Z\-0-9]*)""".r
108 
109   def validUseragent(useragent: String): Boolean = useragent match {
110     case AgentParser(_) => true
111     case _ => false
112   }
113 }
View Code

相关文章:

  • 2022-02-10
  • 2021-11-13
  • 2021-07-07
  • 2021-08-13
  • 2021-11-20
猜你喜欢
  • 2021-12-19
  • 2021-04-12
  • 2022-12-23
  • 2021-12-07
  • 2021-10-09
  • 2021-09-26
  • 2021-08-29
相关资源
相似解决方案