一般情况,实现全局唯一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大小的整数,它的结构如下图:
-
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位datacenterId和5位workerId -
5位(bit)可以表示的最大正整数是$2^{5}-1 = 31$,即可以用0、1、2、3、....31这32个数字,来表示不同的datecenterId或workerId
- 可以部署在$2^{10} = 1024$个节点,包括
-
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 }