原文来自标点符的《高并发环境下生成订单惟一流水号方法:SnowFlake》html
业务需求:java
关于订单号的生成,一些比较简单的方案:linux
一、数据库自增加IDgit
二、时间戳+随机数github
三、时间戳+会员ID算法
四、GUID/UUIDsql
今天要分享的方案:来自twitter的SnowFlake数据库
Twitter-Snowflake算法产生的背景至关简单,为了知足Twitter每秒上万条消息的请求,每条消息都必须分配一条惟一的id,这些id还须要一些大体的顺序(方便客户端排序),而且在分布式系统中不一样机器产生的id必须不一样.Snowflake算法核心把时间戳,工做机器id,序列号(毫秒级时间41位+机器ID 10位+毫秒内序列12位)组合在一块儿。服务器
在上面的字符串中,第一位为未使用(实际上也可做为long的符号位),接下来的41位为毫秒级时间,而后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),而后12位该毫秒内的当前毫秒内的计数,加起来恰好64位,为一个Long型。多线程
除了最高位bit标记为不可用之外,其他三组bit占位都可浮动,看具体的业务需求而定。默认状况下41bit的时间戳能够支持该算法使用到2082年,10bit的工做机器id能够支持1023台机器,序列号支持1毫秒产生4095个自增序列id。下文会具体分析。
Snowflake – 时间戳
这里时间戳的细度是毫秒级,具体代码以下,建议使用64位linux系统机器,由于有vdso,gettimeofday()在用户态就能够完成操做,减小了进入内核态的损耗。
1 2 3 4 5 6 |
uint64_t generateStamp() { timeval tv; gettimeofday(&tv, 0); return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000; } |
默认状况下有41个bit能够供使用,那么一共有T(1llu << 41)毫秒供你使用分配,年份 = T / (3600 * 24 * 365 * 1000) = 69.7年。若是你只给时间戳分配39个bit使用,那么根据一样的算法最后年份 = 17.4年。
Snowflake – 工做机器id
严格意义上来讲这个bit段的使用能够是进程级,机器级的话你可使用MAC地址来惟一标示工做机器,工做进程级可使用IP+Path来区分工做进程。若是工做机器比较少,可使用配置文件来设置这个id是一个不错的选择,若是机器过多配置文件的维护是一个灾难性的事情。
这里的解决方案是须要一个工做id分配的进程,可使用本身编写一个简单进程来记录分配id,或者利用Mysql auto_increment机制也能够达到效果。
工做进程与工做id分配器只是在工做进程启动的时候交互一次,而后工做进程能够自行将分配的id数据落文件,下一次启动直接读取文件里的id使用。这个工做机器id的bit段也能够进一步拆分,好比用前5个bit标记进程id,后5个bit标记线程id之类:D
Snowflake – 序列号
序列号就是一系列的自增id(多线程建议使用atomic),为了处理在同一毫秒内须要给多条消息分配id,若同一毫秒把序列号用完了,则“等待至下一毫秒”。
1 2 3 4 5 6 7 8 |
uint64_t waitNextMs(uint64_t lastStamp) { uint64_t cur = 0; do { cur = generateStamp(); } while (cur <= lastStamp); return cur; } |
整体来讲,是一个很高效很方便的GUID产生算法,一个int64_t字段就能够胜任,不像如今主流128bit的GUID算法,即便没法保证严格的id序列性,可是对于特定的业务,好比用作游戏服务器端的GUID产生会很方便。另外,在多线程的环境下,序列号使用atomic能够在代码实现上有效减小锁的密度。
该项目地址为:https://github.com/twitter/snowflake 是用Scala实现的。核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
/** Copyright 2010-2012 Twitter, Inc.*/ package com.twitter.service.snowflake
import com.twitter.ostrich.stats.Stats import com.twitter.service.snowflake.gen._ import java.util.Random import com.twitter.logging.Logger
/** * An object that generates IDs. * This is broken into a separate class in case * we ever want to support multiple worker threads * per process */ class IdWorker(val workerId: Long, val datacenterId: Long, private val reporter: Reporter, var sequence: Long = 0L) extends Snowflake.Iface { private[this] def genCounter(agent: String) = { Stats.incr("ids_generated") Stats.incr("ids_generated_%s".format(agent)) } private[this] val exceptionCounter = Stats.getCounter("exceptions") private[this] val log = Logger.get private[this] val rand = new Random
val twepoch = 1288834974657L
private[this] val workerIdBits = 5L private[this] val datacenterIdBits = 5L private[this] val maxWorkerId = -1L ^ (-1L << workerIdBits) private[this] val maxDatacenterId = -1L ^ (-1L << datacenterIdBits) private[this] val sequenceBits = 12L
private[this] val workerIdShift = sequenceBits private[this] val datacenterIdShift = sequenceBits + workerIdBits private[this] val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits private[this] val sequenceMask = -1L ^ (-1L << sequenceBits)
private[this] var lastTimestamp = -1L
// sanity check for workerId if (workerId > maxWorkerId || workerId < 0) { exceptionCounter.incr(1) throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0".format(maxWorkerId)) }
if (datacenterId > maxDatacenterId || datacenterId < 0) { exceptionCounter.incr(1) throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0".format(maxDatacenterId)) }
log.info("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId)
def get_id(useragent: String): Long = { if (!validUseragent(useragent)) { exceptionCounter.incr(1) throw new InvalidUserAgentError }
val id = nextId() genCounter(useragent)
reporter.report(new AuditLogEntry(id, useragent, rand.nextLong)) id }
def get_worker_id(): Long = workerId def get_datacenter_id(): Long = datacenterId def get_timestamp() = System.currentTimeMillis
protected[snowflake] def nextId(): Long = synchronized { var timestamp = timeGen()
if (timestamp < lastTimestamp) { exceptionCounter.incr(1) log.error("clock is moving backwards. Rejecting requests until %d.", lastTimestamp); throw new InvalidSystemClock("Clock moved backwards. Refusing to generate id for %d milliseconds".format( lastTimestamp - timestamp)) }
if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp) } } else { sequence = 0 }
lastTimestamp = timestamp ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence }
protected def tilNextMillis(lastTimestamp: Long): Long = { var timestamp = timeGen() while (timestamp <= lastTimestamp) { timestamp = timeGen() } timestamp }
protected def timeGen(): Long = System.currentTimeMillis()
val AgentParser = """([a-zA-Z][a-zA-Z\-0-9]*)""".r
def validUseragent(useragent: String): Boolean = useragent match { case AgentParser(_) => true case _ => false } } |
由UC实现的JAVA版本代码(略有修改)
来源:https://github.com/sumory/uc/blob/master/src/com/sumory/uc/id/IdWorker.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
package com.sumory.uc.id;
import java.math.BigInteger;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
/** * 42位的时间前缀+10位的节点标识+12位的sequence避免并发的数字(12位不够用时强制获得新的时间前缀) * <p> * <b>对系统时间的依赖性很是强,须要关闭ntp的时间同步功能,或者当检测到ntp时间调整后,拒绝分配id。 * * @author sumory.wu * @date 2012-2-26 下午6:40:28 */ public class IdWorker { private final static Logger logger = LoggerFactory.getLogger(IdWorker.class);
private final long workerId; private final long snsEpoch = 1330328109047L;// 起始标记点,做为基准 private long sequence = 0L;// 0,并发控制 private final long workerIdBits = 10L;// 只容许workid的范围为:0-1023 private final long maxWorkerId = -1L ^ -1L << this.workerIdBits;// 1023,1111111111,10位 private final long sequenceBits = 12L;// sequence值控制在0-4095
private final long workerIdShift = this.sequenceBits;// 12 private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;// 22 private final long sequenceMask = -1L ^ -1L << this.sequenceBits;// 4095,111111111111,12位
private long lastTimestamp = -1L;
public IdWorker(long workerId) { super(); if (workerId > this.maxWorkerId || workerId < 0) {// workid < 1024[10位:2的10次方] throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", this.maxWorkerId)); } this.workerId = workerId; }
public synchronized long nextId() throws Exception { long timestamp = this.timeGen(); if (this.lastTimestamp == timestamp) {// 若是上一个timestamp与新产生的相等,则sequence加一(0-4095循环),下次再使用时sequence是新值 //System.out.println("lastTimeStamp:" + lastTimestamp); this.sequence = this.sequence + 1 & this.sequenceMask; if (this.sequence == 0) { timestamp = this.tilNextMillis(this.lastTimestamp);// 从新生成timestamp } } else { this.sequence = 0; } if (timestamp < this.lastTimestamp) { logger.error(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp))); throw new Exception(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp))); }
this.lastTimestamp = timestamp; // 生成的timestamp return timestamp - this.snsEpoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.sequence; }
/** * 保证返回的毫秒数在参数以后 * * @param lastTimestamp * @return */ private long tilNextMillis(long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; }
/** * 得到系统当前毫秒数 * * @return */ private long timeGen() { return System.currentTimeMillis(); }
public static void main(String[] args) throws Exception { IdWorker iw1 = new IdWorker(1); IdWorker iw2 = new IdWorker(2); IdWorker iw3 = new IdWorker(3);
// System.out.println(iw1.maxWorkerId); // System.out.println(iw1.sequenceMask);
System.out.println("---------------------------");
long workerId = 1L; long twepoch = 1330328109047L; long sequence = 0L;// 0 long workerIdBits = 10L; long maxWorkerId = -1L ^ -1L << workerIdBits;// 1023,1111111111,10位 long sequenceBits = 12L;
long workerIdShift = sequenceBits;// 12 long timestampLeftShift = sequenceBits + workerIdBits;// 22 long sequenceMask = -1L ^ -1L << sequenceBits;// 4095,111111111111,12位
long ct = System.currentTimeMillis();// 1330328109047L;// System.out.println(ct);
System.out.println(ct - twepoch); System.out.println(ct - twepoch << timestampLeftShift);// 左移22位:*2的22次方 System.out.println(workerId << workerIdShift);// 左移12位:*2的12次方 System.out.println("哈哈"); System.out.println(ct - twepoch << timestampLeftShift | workerId << workerIdShift); long result = ct - twepoch << timestampLeftShift | workerId << workerIdShift | sequence;// 取时间的低40位 | (小于1024:只有12位)的低12位 | 计算的sequence System.out.println(result);
System.out.println("---------------"); for (int i = 0; i < 10; i++) { System.out.println(iw1.nextId()); }
Long t1 = 66708908575965184l; Long t2 = 66712718304231424l; Long t3 = 66715908575739904l; Long t4 = 66717361423925248l; System.out.println(Long.toBinaryString(t1)); System.out.println(Long.toBinaryString(t2)); System.out.println(Long.toBinaryString(t3)); System.out.println(Long.toBinaryString(t4)); //1110110011111111011001100001111100 0001100100 000000000000 //1110110100000010110111010010010010 0001100100 000000000000 //1110110100000101110000111110111110 0001100100 000000000000 //1110110100000111000101100011010000 0001100100 000000000000 System.out.println(Long.toBinaryString(66706920114753536l)); //1110110011111101100101110010010110 0000000001 000000000000
String a = "0001100100";//输入数值 BigInteger src = new BigInteger(a, 2);//转换为BigInteger类型 System.out.println(src.toString());//转换为2进制并输出结果
} } |
Go语言版本:https://github.com/sumory/idgen
Python语言版本:https://github.com/erans/pysnowflake