高併發狀況下,如何生成分佈式全局id

1.使用UUID生成全局id

UUID是一個字符串並且沒有順序,因此不適合作主鍵,能夠 作 token 使用。java

利用全球惟一UUID生成訂單號 UUID基本概念: UUID是指在一臺機器上生成的數字,它保證對在同一時空中的全部機器都是惟一的。mysql

UUID組成部分:當前日期和時間+時鐘序列+隨機數+全局惟一的IEEE機器識別號 全局惟一的IEEE機器識別號:若是有網卡,從網卡MAC地址得到,沒有網卡以其餘方式得到。web

 

優勢: 簡單,代碼方便 生成ID性能很是好,基本不會有性能問題 全球惟一,在碰見數據遷移,系統數據合併,或者數據庫變動等狀況下,能夠從容應對redis

缺點: 沒有排序,沒法保證趨勢遞增 UUID每每是使用字符串存儲,查詢的效率比較低 存儲空間比較大,若是是海量數據庫,就須要考慮存儲量的問題。 傳輸數據量大算法

通常UUID在生成Token領域使用比較多spring

2.基於數據庫自增或者序列生成全局id

若是使用數據庫id自增生成訂單號的話,若是數據庫是集羣的話 則有可能生成相同的訂單號。sql

因此若是咱們使用數據庫id 自增作爲全局 id 的話咱們須要設置步長,步長表示每次自動增加的數量。數據庫

舉栗子:安全

假如如今有 3 臺 MySql 數據庫作集羣,mysql1,mysql2,mysql3。服務器

設置mysql1數據庫id初始值爲0,mysql數據庫id初始值爲1,mysql3數據庫id初始值爲2。咱們這時候須要設置步長爲3,

mysql1每次自增結果爲  0,3,6,9,12,15

mysql2每次自動結果爲  1,4,7,10,13,16

mysql3每次自動結果爲  2,5,8,11,14,17

........

這就是最後產生自增的結果,可是這種方法還有一個缺點,就是若是後期增長數據庫服務器集羣數量的話,mysql 步長沒法擴展。因此使用這種方法生成全局id,須要前期肯定好mysql數據庫集羣的數量,否則那到後期擴展集羣數量會致使生成步長規則發生改變,可能會產生重複的id。

在數據庫集羣環境下,默認自增方式存在問題,由於都是從1開始自增,可能會存在重複,應該設置每臺節點自增步長不一樣。

查詢自增的步長

SHOW VARIABLES LIKE 'auto_increment%'

修改自增的步長

SET @@auto_increment_increment=10;

修改起始值

SET @@auto_increment_offset=5;

 

3.基於redis生成全局id

由於Redis是單線的,天生保證原子性,可使用Redis的原子操做 INCR和INCRBY來實現

優勢: 不依賴於數據庫,靈活方便,且性能優於數據庫。 數字ID自然排序,對分頁或者須要排序的結果頗有幫助。

缺點: 若是系統中沒有Redis,還須要引入新的組件,增長系統複雜度。 須要編碼和配置的工做量比較大。

注意:在Redis集羣狀況下,一樣和Redis同樣須要設置不一樣的增加步長,同時key必定要設置有效期 可使用Redis集羣來獲取更高的吞吐量。

假如一個集羣中有5臺Redis。能夠初始化每臺Redis的值分別是1,2,3,4,5,而後步長都是5。

各個Redis生成的ID爲:

A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25

比較適合使用Redis來生成天天從0開始的流水號。

好比訂單號=日期+當日自增加號。能夠天天在Redis中生成一個Key,使用INCR進行累加。 若是生成的訂單號超過自增增加的話,能夠採用前綴+自增+而且設置有效期。

使用 redis 實現全局id生成,使用自動填充的方法實現。

 1 package com.example.controller;
 2 
 3 import java.text.SimpleDateFormat;
 4 import java.util.Date;
 5 
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.data.redis.core.RedisTemplate;
 8 import org.springframework.data.redis.support.atomic.RedisAtomicLong;
 9 import org.springframework.web.bind.annotation.RequestMapping;
10 import org.springframework.web.bind.annotation.RestController;
11 
12 /**
13  * 基於Redis 實現分佈式全局id
14  */
15 @RestController
16 public class OrderController {
17 
18     @Autowired
19     private RedisTemplate redisTemplate;
20 
21     // 基於Redis 實現分佈式全局id原理
22 
23     /**
24      * 15 18位 前綴=當前日期=2018112921303030-5位自增id(高併發請下 先天性安全) 00001<br>
25      * 00010<br>
26      * 00100<br>
27      * 01000<br>
28      * 11000<br>
29      * 在相同毫秒狀況下,最多隻能生成10萬-1=99999訂單號<br>
30      * 假設:雙11每毫秒99萬筆 <br>
31      * 提早生成號訂單號碼存放在redis中
32      * <p>
33      * 9.9萬*1000=900萬<br>
34      * 考慮失效時間問題 24小時
35      *
36      * @return
37      */
38     @RequestMapping("/order")
39     public String order(String key) {
40         RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
41         for (int i = 0; i < 100; i++) {
42             long incrementAndGet = redisAtomicLong.incrementAndGet();
43             //1$5 表示是5位數 0表示以0填充 d 表示正數
44             String orderId = prefix() + "-" + String.format("%1$05d", incrementAndGet);
45             String orderSQL = "insert into orderNumber value('" + orderId + "');";
46             System.out.println(orderSQL);
47         }
48 
49         return "success";
50     }
51 
52     
53     @RequestMapping("/order1")
54     public String order1(String key) {
55         RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
56         // // 起始值
57         redisAtomicLong.set(1);
58         // 設置步長加10
59         redisAtomicLong.addAndGet(9);
60         return redisAtomicLong.incrementAndGet() + "";
61     }
62 
63     public static String prefix() {
64         String temp_str = "";
65         Date dt = new Date();
66         // 最後的aa表示「上午」或「下午」 HH表示24小時制 若是換成hh表示12小時制
67         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
68         temp_str = sdf.format(dt);
69         return temp_str;
70     }
71 
72 }

 

4.基於Twitter的雪花算法(snowflake)生成全局ID

  1 package com.example.controller;
  2 
  3 /**
  4  * Twitter_Snowflake<br>
  5  * SnowFlake的結構以下(每部分用-分開):<br>
  6  * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 -
  7  * 000000000000 <br>
  8  * 1位標識,因爲long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,因此id通常是正數,最高位是0<br>
  9  * 41位時間截(毫秒級),注意,41位時間截不是存儲當前時間的時間截,而是存儲時間截的差值(當前時間截 - 開始時間截)
 10  * 獲得的值),這裏的的開始時間截,通常是咱們的id生成器開始使用的時間,由咱們程序來指定的(以下下面程序IdWorker類的startTime屬性)。
 11  * 41位的時間截,可使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 12  * 10位的數據機器位,能夠部署在1024個節點,包括5位datacenterId和5位workerId<br>
 13  * 12位序列,毫秒內的計數,12位的計數順序號支持每一個節點每毫秒(同一機器,同一時間截)產生4096個ID序號<br>
 14  * 加起來恰好64位,爲一個Long型。<br>
 15  * SnowFlake的優勢是,總體上按照時間自增排序,而且整個分佈式系統內不會產生ID碰撞(由數據中心ID和機器ID做區分),而且效率較高,經測試,
 16  * SnowFlake每秒可以產生26萬ID左右。
 17  */
 18 public class SnowflakeIdWorker {
 19 
 20     // ==============================Fields===========================================
 21     /** 開始時間截 (2015-01-01) */
 22     private final long twepoch = 1420041600000L;
 23 
 24     /** 機器id所佔的位數 */
 25     private final long workerIdBits = 5L;
 26 
 27     /** 數據標識id所佔的位數 */
 28     private final long datacenterIdBits = 5L;
 29 
 30     /** 支持的最大機器id,結果是31 (這個移位算法能夠很快的計算出幾位二進制數所能表示的最大十進制數) */
 31     private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 32 
 33     /** 支持的最大數據標識id,結果是31 */
 34     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 35 
 36     /** 序列在id中佔的位數 */
 37     private final long sequenceBits = 12L;
 38 
 39     /** 機器ID向左移12位 */
 40     private final long workerIdShift = sequenceBits;
 41 
 42     /** 數據標識id向左移17位(12+5) */
 43     private final long datacenterIdShift = sequenceBits + workerIdBits;
 44 
 45     /** 時間截向左移22位(5+5+12) */
 46     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 47 
 48     /** 生成序列的掩碼,這裏爲4095 (0b111111111111=0xfff=4095) */
 49     private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 50 
 51     /** 工做機器ID(0~31) */
 52     private long workerId;
 53 
 54     /** 數據中心ID(0~31) */
 55     private long datacenterId;
 56 
 57     /** 毫秒內序列(0~4095) */
 58     private long sequence = 0L;
 59 
 60     /** 上次生成ID的時間截 */
 61     private long lastTimestamp = -1L;
 62 
 63     // ==============================Constructors=====================================
 64     /**
 65      * 構造函數
 66      * 
 67      * @param workerId
 68      *            工做ID (0~31)
 69      * @param datacenterId
 70      *            數據中心ID (0~31)
 71      */
 72     public SnowflakeIdWorker(long workerId, long datacenterId) {
 73         if (workerId > maxWorkerId || workerId < 0) {
 74             throw new IllegalArgumentException(
 75                     String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
 76         }
 77         if (datacenterId > maxDatacenterId || datacenterId < 0) {
 78             throw new IllegalArgumentException(
 79                     String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
 80         }
 81         this.workerId = workerId;
 82         this.datacenterId = datacenterId;
 83     }
 84 
 85     // ==============================Methods==========================================
 86     /**
 87      * 得到下一個ID (該方法是線程安全的)
 88      * 
 89      * @return SnowflakeId
 90      */
 91     public synchronized long nextId() {
 92         long timestamp = timeGen();
 93 
 94         // 若是當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過這個時候應當拋出異常
 95         if (timestamp < lastTimestamp) {
 96             throw new RuntimeException(String.format(
 97                     "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
 98         }
 99 
100         // 若是是同一時間生成的,則進行毫秒內序列
101         if (lastTimestamp == timestamp) {
102             sequence = (sequence + 1) & sequenceMask;
103             // 毫秒內序列溢出
104             if (sequence == 0) {
105                 // 阻塞到下一個毫秒,得到新的時間戳
106                 timestamp = tilNextMillis(lastTimestamp);
107             }
108         }
109         // 時間戳改變,毫秒內序列重置
110         else {
111             sequence = 0L;
112         }
113 
114         // 上次生成ID的時間截
115         lastTimestamp = timestamp;
116 
117         // 移位並經過或運算拼到一塊兒組成64位的ID
118         return ((timestamp - twepoch) << timestampLeftShift) //
119                 | (datacenterId << datacenterIdShift) //
120                 | (workerId << workerIdShift) //
121                 | sequence;
122     }
123 
124     /**
125      * 阻塞到下一個毫秒,直到得到新的時間戳
126      * 
127      * @param lastTimestamp
128      *            上次生成ID的時間截
129      * @return 當前時間戳
130      */
131     protected long tilNextMillis(long lastTimestamp) {
132         long timestamp = timeGen();
133         while (timestamp <= lastTimestamp) {
134             timestamp = timeGen();
135         }
136         return timestamp;
137     }
138 
139     /**
140      * 返回以毫秒爲單位的當前時間
141      * 
142      * @return 當前時間(毫秒)
143      */
144     protected long timeGen() {
145         return System.currentTimeMillis();
146     }
147 
148     // ==============================Test=============================================
149     /** 測試 */
150     public static void main(String[] args) {
151         SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
152         for (int i = 0; i < 100; i++) {
153             long id = idWorker.nextId();
154             String insertSQL = "insert into orderNumber value('" + id + "');";
155             System.out.println(insertSQL);
156         }
157     }
158 }

 

能最好

相關文章
相關標籤/搜索