Twitter-snowflake生成唯一的id算法

snowflake生成唯一的id算法

Posted by Hzy on April 17, 2020

这两天都在想如何生成唯一的iD的问题,下面是一些整理。

1.大部分文章都会介绍以下几种方案:

1.1 采用数据库自增ID

优点:

  • 实现简单,ID有序

缺点:

  • 主要问题就在于,当场景需要对ID需要进行集成和切割时,如何保证ID不冲突。
  • 例如: 新旧系统都存在的时候。
  • 例如: 需要插入指定ID进行记录的时候。
1.2 UUID

优点:

  • 本地生成,生成简单,性能好,没有高可用风险

缺点:

  • 长度过长,存储冗余,且无序不可读,查询效率低
1.3 Redis生成ID,这个方法没试过,需要用redis的时候试试

优点:

  • 不依赖于数据库,灵活方便,且性能优于数据库;有序,对分页或者需要排序的结果很有帮助。
  • 对于分布式来说,可以初始化每台redis的初始值,步长,然后按照步长递增。
1
2
3
4
5
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的系统,需要引入额外组件。
1.4 snowflake算法,本文重点。

  • 1bit:留着备用
  • 41bit: 时间戳:2的41次方为:2199023255552,假设当前时间为2020-04-18 01:38:18,转为时间戳1587145098000, 1587145098000+2199023255552=3786168353552,3786168353552代表2089-12-23 17:25:53,可以使用69年。
  • 10bit:记录机器id,2的10次方是1024台,机器可以根据地域和工作机器在进行细分。

其实机器iD和时间戳都可以根据自己的需求定义,41位呢是因为刚好可以塞到数据BIGINT类型中。

接下来是python中的实现

1.定义基本的变量,分布区域,机器码,序列的bit

1
2
3
region_id_bits = 2 #机器分布的区域分配的bit -> 2**2=4
worker_id_bits = 10 # #机器分配的bit ->2**10=1024
sequence_bits = 11 # 序列分配的bit ->2**11=2028

2.计算最大值,及能取的最大值

1
2
3
4
# 能取的最大值,位运算+1
MAX_REGION_ID = -1 ^ (-1 << region_id_bits)
MAX_WORKER_ID = -1 ^ (-1 << worker_id_bits)
SEQUENCE_MASK = -1 ^ (-1 << sequence_bits)

3.定义一个元年,想Twitter就是1288834974657,时间戳从-11开始,序列号为0

1
2
3
self.twepoch = 1288834974657
self.last_timestamp = -1
self.sequence = 0

4.生成id的函数

  • 判断时间是否同步,分布式的机器时间必须一致,若是单台机器则没这问题。
  • 判断当前序列号是否超出最大值:是则寻找新的时间戳,不是则序列号+1。
  • 最后对时间戳,区域id,机器id和序列号,进行偏移然后拼接。
1
2
3
4
5
6
7
8
9
# 这里可能会点看不太懂,其实就是用左移动运算符(<<)填充0。
# 把时间戳填充成64位
# 把区域id,机器id同样操作
# 最后使用或运算将他们加起来,形成一个64bit长度的唯一id.
return (
    (timestamp - self.twepoch) << Snowflake.TIMESTAMP_LEFT_SHIFT 
    |(padding_num << Snowflake.REGION_ID_SHIFT) 
    |(self.worker_id << Snowflake.WORKER_ID_SHIFT) 
    |self.sequence )

最后附上完成的代码:

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
class Snowflake(object):
    region_id_bits = 2
    worker_id_bits = 10
    sequence_bits = 11

    # 能取的最大值,位运算+1
    MAX_REGION_ID = -1 ^ (-1 << region_id_bits)
    MAX_WORKER_ID = -1 ^ (-1 << worker_id_bits)
    SEQUENCE_MASK = -1 ^ (-1 << sequence_bits)

    # 移位偏移计算,计算位数,用于之后的异或运算
    WORKER_ID_SHIFT = sequence_bits
    REGION_ID_SHIFT = sequence_bits + worker_id_bits
    TIMESTAMP_LEFT_SHIFT = (sequence_bits + worker_id_bits + region_id_bits)

    def __init__(self, worker_id, region_id=0):
        self.twepoch = 1288834974657
        self.last_timestamp = -1
        self.sequence = 0

        assert 0 <= worker_id <= Snowflake.MAX_WORKER_ID
        assert 0 <= region_id <= Snowflake.MAX_REGION_ID

        self.worker_id = worker_id
        self.region_id = region_id

        self.lock = threading.Lock()

    def generate(self, bus_id=None):
        return self.next_id(
            True if bus_id is not None else False,
            bus_id if bus_id is not None else 0
        )

    def next_id(self, is_padding, bus_id):
        with self.lock:
            timestamp = self.get_time()
            padding_num = self.region_id

            if is_padding:
                padding_num = bus_id

            if timestamp < self.last_timestamp:
                try:
                    raise ValueError(
                        'Clock moved backwards. Refusing to'
                        'generate id for {0} milliseconds.'.format(
                            self.last_timestamp - timestamp
                        )
                    )
                except ValueError:
                    print(sys.exc_info()[2])

            if timestamp == self.last_timestamp:
                self.sequence = (self.sequence + 1) & Snowflake.SEQUENCE_MASK
                if self.sequence == 0:
                    timestamp = self.tail_next_millis(self.last_timestamp)
            else:
                self.sequence = random.randint(0, 9)

            self.last_timestamp = timestamp

            print(timestamp - self.twepoch << Snowflake.TIMESTAMP_LEFT_SHIFT)
            print(padding_num << Snowflake.REGION_ID_SHIFT)
            print(self.worker_id << Snowflake.WORKER_ID_SHIFT)
            print(self.sequence)
            return (
                    (timestamp - self.twepoch) << Snowflake.TIMESTAMP_LEFT_SHIFT |
                    (padding_num << Snowflake.REGION_ID_SHIFT) |
                    (self.worker_id << Snowflake.WORKER_ID_SHIFT) |
                    self.sequence
            )

    def tail_next_millis(self, last_timestamp):
        timestamp = self.get_time()
        while timestamp <= last_timestamp:
            timestamp = self.get_time()
        return timestamp

    def get_time(self):
        return int(time.time() * 1000)