一.訂單號生成的原則:
1.全域性的唯一性
2.自增長
3.長度的要求
4.具有一定的可讀性
5.保密,不可推測性
6.效率性
二.實現方案
常見的ID生成策略。 1. 資料庫自增長序列或欄位 2. UUID 3. UUID的變種*【UUID to Int64;NHibernate在其主鍵生成方式中提供了Comb演算法(combined guid/timestamp)】 4. Redis生成ID 5. Twitter的snowflake演算法 6. 利用zookeeper的znode生成唯一ID 7. MongoDB的ObjectId
三.高併發下怎樣生成唯一的訂單號?
如果沒有併發,訂單號只在一個執行緒內產生,那麼由於程式是順序執行的,不同訂單的生成時間一定不同,因此用時間就可以區分各個訂單。
如果存在併發,且訂單號是由一個程序中的多個執行緒產生的,那麼只要把執行緒ID新增到序列號中就可以保證訂單號唯一。
如果存在併發,且訂單號是由同一臺主機中的多個程序產生的,那麼只要把程序ID新增到序列號中就可以保證訂單號唯一。
如果存在併發,且訂單號是由不同臺主機產生的,那麼MAC地址、IP地址或CPU序列號等能夠區分主機的號碼新增到序列號中就可以保證訂單號唯一。
1. 機器碼(3位, 分散式節點),年月日分時秒(12位),遞增的序列(4位),當併發遞增序列超過4位時,秒數+1,序列從0開始計時,這樣每秒支援9999個訂單號生成,隔天序列清為0.
2.後臺統一生成的訂單號後,推入redis,一次性推個幾十W個,檢查剩餘多少後,再推,也可以保證高併發的場景。
四.Twitter開源分散式自增ID演算法snowflake
1.snowflake簡介
網際網路快速發展的今天,分散式應用系統已經見怪不怪,在分散式系統中,我們需要各種各樣的ID,既然是ID那麼必然是要保證全域性唯一,除此之外,不同當業務還需要不同的特性,比如像併發巨大的業務要求ID生成效率高,吞吐大;比如某些銀行類業務,需要按每日日期制定交易流水號;又比如我們希望使用者的ID是隨機的,無序的,純數字的,且位數長度是小於10位的。等等,不同的業務場景需要的ID特性各不一樣,於是,衍生了各種ID生成器,但大多數利用資料庫控制ID的生成,效能受資料庫併發能力限制,那麼有沒有一款不需要依賴任何中介軟體(如資料庫,分散式快取服務等)的ID生成器呢?本著取之於開源,用之於開源的原則,今天,特此介紹Twitter開源的一款分散式自增ID演算法snowflake,並附上演算法原理推導和演算過程!
snowflake演算法是一款本地生成的(ID生成過程不依賴任何中介軟體,無網路通訊),保證ID全域性唯一,並且ID總體有序遞增,效能每秒生成300w+。
2.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(最大支援2^5=32個,二進位制表示從00000-11111,也即是十進位制0-31),和5位workerId(最大支援2^5=32個,原理同datacenterId),所以datacenterId*workerId最多支援部署1024個節點,最後12位是毫秒內的計數(12位的計數順序號支援每個節點每毫秒產生2^12=4096個ID序號).
所有位數加起來共64位,恰好是一個Long型(轉換為字串長度為18).
單臺機器例項,透過時間戳保證前41位是唯一的,分散式系統多臺機器例項下,透過對每個機器例項分配不同的datacenterId和workerId避免中間的10位碰撞。最後12位每毫秒從0遞增生產ID,再提一次:每毫秒最多生成4096個ID,每秒可達4096000個。理論上,只要CPU計算能力足夠,單機每秒可生產400多萬個,實測300w+,效率之高由此可見。
(該節改編自:http://www.cnblogs.com/relucent/p/4955340.html)
3.snowflake演算法原始碼(java版)
@ToString@Slf4jpublic class SnowflakeIdFactory { private final long twepoch = 1288834974657L; private final long workerIdBits = 5L; private final long datacenterIdBits = 5L; private final long maxWorkerId = -1L ^ (-1L << workerIdBits); private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); private final long sequenceBits = 12L; private final long workerIdShift = sequenceBits; private final long datacenterIdShift = sequenceBits + workerIdBits; private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private final long sequenceMask = -1L ^ (-1L << sequenceBits); private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public SnowflakeIdFactory(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; } public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { //伺服器時鐘被調整了,ID生成器停止服務. 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; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen() { return System.currentTimeMillis(); } public static void testProductIdByMoreThread(int dataCenterId, int workerId, int n) throws InterruptedException { List<Thread> tlist = new ArrayList<>(); Set<Long> setAll = new HashSet<>(); CountDownLatch cdLatch = new CountDownLatch(10); long start = System.currentTimeMillis(); int threadNo = dataCenterId; Map<String,SnowflakeIdFactory> idFactories = new HashMap<>(); for(int i=0;i<10;i++){ //用執行緒名稱做map key. idFactories.put("snowflake"+i,new SnowflakeIdFactory(workerId, threadNo++)); } for(int i=0;i<10;i++){ Thread temp =new Thread(new Runnable() { @Override public void run() { Set<Long> setId = new HashSet<>(); SnowflakeIdFactory idWorker = idFactories.get(Thread.currentThread().getName()); for(int j=0;j<n;j++){ setId.add(idWorker.nextId()); } synchronized (setAll){ setAll.addAll(setId); log.info("{}生產了{}個id,併成功加入到setAll中.",Thread.currentThread().getName(),n); } cdLatch.countDown(); } },"snowflake"+i); tlist.add(temp); } for(int j=0;j<10;j++){ tlist.get(j).start(); } cdLatch.await(); long end1 = System.currentTimeMillis() - start; log.info("共耗時:{}毫秒,預期應該生產{}個id, 實際合併總計生成ID個數:{}",end1,10*n,setAll.size()); } public static void testProductId(int dataCenterId, int workerId, int n){ SnowflakeIdFactory idWorker = new SnowflakeIdFactory(workerId, dataCenterId); SnowflakeIdFactory idWorker2 = new SnowflakeIdFactory(workerId+1, dataCenterId); Set<Long> setOne = new HashSet<>(); Set<Long> setTow = new HashSet<>(); long start = System.currentTimeMillis(); for (int i = 0; i < n; i++) { setOne.add(idWorker.nextId());//加入set } long end1 = System.currentTimeMillis() - start; log.info("第一批ID預計生成{}個,實際生成{}個<<<<*>>>>共耗時:{}",n,setOne.size(),end1); for (int i = 0; i < n; i++) { setTow.add(idWorker2.nextId());//加入set } long end2 = System.currentTimeMillis() - start; log.info("第二批ID預計生成{}個,實際生成{}個<<<<*>>>>共耗時:{}",n,setTow.size(),end2); setOne.addAll(setTow); log.info("合併總計生成ID個數:{}",setOne.size()); } public static void testPerSecondProductIdNums(){ SnowflakeIdFactory idWorker = new SnowflakeIdFactory(1, 2); long start = System.currentTimeMillis(); int count = 0; for (int i = 0; System.currentTimeMillis()-start<1000; i++,count=i) { /** 測試方法一: 此用法純粹的生產ID,每秒生產ID個數為300w+ */ idWorker.nextId(); /** 測試方法二: 在log中列印,同時獲取ID,此用法生產ID的能力受限於log.error()的吞吐能力. * 每秒徘徊在10萬左右. */ //log.error("{}",idWorker.nextId()); } long end = System.currentTimeMillis()-start; System.out.println(end); System.out.println(count); } public static void main(String[] args) { /** case1: 測試每秒生產id個數? * 結論: 每秒生產id個數300w+ */ //testPerSecondProductIdNums(); /** case2: 單執行緒-測試多個生產者同時生產N個id,驗證id是否有重複? * 結論: 驗證透過,沒有重複. */ //testProductId(1,2,10000);//驗證透過! //testProductId(1,2,20000);//驗證透過! /** case3: 多執行緒-測試多個生產者同時生產N個id, 全部id在全域性範圍內是否會重複? * 結論: 驗證透過,沒有重複. */ try { testProductIdByMoreThread(1,2,100000);//單機測試此場景,效能損失至少折半! } catch (InterruptedException e) { e.printStackTrace(); } }}
測試用例
/** case1: 測試每秒生產id個數? * 結論: 每秒生產id個數300w+ *///testPerSecondProductIdNums(); /** case2: 單執行緒-測試多個生產者同時生產N個id,驗證id是否有重複? * 結論: 驗證透過,沒有重複. *///testProductId(1,2,10000);//驗證透過!//testProductId(1,2,20000);//驗證透過! /** case3: 多執行緒-測試多個生產者同時生產N個id, 全部id在全域性範圍內是否會重複? * 結論: 驗證透過,沒有重複. */try { testProductIdByMoreThread(1,2,100000);//單機測試此場景,效能損失至少折半!} catch (InterruptedException e) { e.printStackTrace();}
4.snowflake演算法推導和演算過程說明:演算使用的物件例項:SnowflakeIdFactory idWorker = new SnowflakeIdFactory(1, 2);執行時資料workerId=1,datacenterId=2,分別表示機器例項的生產者編號,資料中心編號;sequence=0表示每毫秒生產ID從0開始計數遞增;以下演算基於時間戳=1482394743339時刻進行推導。一句話描述:以下演算模擬了1482394743339這一毫秒時刻,workerId=1,datacenterId=2的id生成器,生產第一個id的過程。