關於在高併發下生成訂單號的策略

高併發環境下生成訂單惟一流水號方法:SnowFlake

原文來自標點符《高併發環境下生成訂單惟一流水號方法:SnowFlake》html

業務需求:java

  • 訂單號不能重複
  • 訂單號沒有規則,即編碼規則不能加入任何和公司運營相關的數據,外部人員沒法經過訂單ID猜想到訂單量。不能被遍歷。
  • 訂單號長度固定,且不能太長
  • 易讀,易溝通,不要出現數字字母換亂現象
  • 生成耗時

關於訂單號的生成,一些比較簡單的方案:linux

一、數據庫自增加IDgit

  • 優點:無需編碼
  • 缺陷:
    • 大表不能作水平分表,不然插入刪除時容易出現問題
    • 高併發下插入數據須要加入事務機制
    • 在業務操做父、子表(關聯表)插入時,先要插入父表,再插入子表

二、時間戳+隨機數github

  • 優點:編碼簡單
  • 缺陷:隨機數存在重複問題,即便在相同的時間戳下。每次插入數據庫前須要校驗下是否已經存在相同的數值。

三、時間戳+會員ID算法

  • 優點:同一時間,一個用戶不會存在兩張訂單
  • 缺陷:會員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

相關文章
相關標籤/搜索