关于在高并发下生成订单号的策略

高并发环境下生成订单惟一流水号方法: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

相关文章
相关标签/搜索