要求:

  • 全局唯一性
  • 有序递增
  • 信息安全
  • 高性能

数据库自增ID


单体应用广泛使用,最简单的实现方式是使用数据库的主键id自增策略,如 MySQLauto_increment。如果两台数据库分别设置不同步长,可以生成不重复ID,从而实现高可用。


优点 :

  • 非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。

缺点 :

  • 强依赖 DB,当 DB 异常时整个系统不可用,属于致命问题。
  • ID发号性能瓶颈限制在单台 MySQL 的读写性能。
  • 分库分表后,同一数据表的自增 ID 容易重复。
  • 数据容易被爬虫获取,直接按照顺序下载指定URL即可,比如单日销量等

    Redis实现ID

通过 Redis 的 INCR / INCRBY 自增原子操作命令,能保证生成的ID肯定是唯一有序的,本质上实现方式与数据库一致。


优点 :

  • 整体吞吐量比数据库要高。

缺点 :

  • 由于redis是内存的KV数据库,即使有AOF和RDB,但是依然会存在数据丢失,导致获取的最新ID不准确,有可能会造成ID重复。

适用场景:

  • 比较适合计数场景,如埋点、用户访问量等。

    UUID


    UUID 包含网卡 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。其标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,例如:550e8400-e29b-41d4-a716-446655440000。


优点:

  • 本地生成,没有经过网络I/O,高性能

缺点:

  • 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
  • 作为主键时会存在一些问题,如做DB主键的场景下,UUID就非常不适用:
  • 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。
  • 官方有明确的建议主键要尽量越短越好,36个字符长度的 UUID 也不符合要求。
  • 信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
  • UUID的无序性可能会引起数据位置频繁变动,严重影响性能。


适用场景:


对存储空间没有严格性能要求的都能够适用,比如各种链路追踪、日志存储等。


MongoDB的ObjectId

MongoDB 的主键类型 ObjectID 也是一种ID生成方案,如:5349b4ddd2781d08c09890f3, 它看起来是一个包含24个字符的字符串,实际采用12个字节来存储。

ObjectId.png
/>它使用4个字节代表时间戳,3个字节代表机器ID,2个字节代表机器进程ID,然后3个字节代表自增值。
,可以容纳更多的信息。



### snowflake算法生成ID


snowflake.webp
snowflake.webp

snowflake结构图

- 1bit:符号位,固定是0,表示全部ID都是正整数
- 41bit:毫秒数时间差,从指定的日期算起,够用69年,我们知道用Long类型表示的时间戳是从1970-01-01 00:00:00开始算起的,这里的时间戳可以指定日期,如2020-01-01 00:00:00
- 10bit:机器ID,有异地部署,多集群的也可以配置,需要线下规划好各地机房,各集群,各实例ID的编号
- 12bit:序列ID,前面都相同的话,最多可以支持到4096个



优点:


- 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
- 可以根据自身业务特性分配bit位,非常灵活。



缺点:


- 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。



代码地址https://github.com/pengfeidai/go-snowflake

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
const (
epoch = int64(1577808000000) // 设置起始时间(时间戳/毫秒):2020-01-01 00:00:00,有效期69年
timestampBits = uint(41) // 时间戳占用位数
datacenteridBits = uint(5) // 数据中心id所占位数
workeridBits = uint(5) // 机器id所占位数
sequenceBits = uint(12) // 序列所占的位数
timestampMax = int64(-1 ^ (-1 << timestampBits)) // 时间戳最大值
datacenteridMax = int64(-1 ^ (-1 << datacenteridBits)) // 支持的最大数据中心id数量
workeridMax = int64(-1 ^ (-1 << workeridBits)) // 支持的最大机器id数量
sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // 支持的最大序列id数量
workeridShift = sequenceBits // 机器id左移位数
datacenteridShift = sequenceBits + workeridBits // 数据中心id左移位数
timestampShift = sequenceBits + workeridBits + datacenteridBits // 时间戳左移位数
)
type Snowflake struct {
sync.Mutex
timestamp int64
workerid int64
datacenterid int64
sequence int64
}
func NewSnowflake(datacenterid, workerid int64) (*Snowflake, error) {
if datacenterid < 0 || datacenterid > datacenteridMax {
return nil, fmt.Errorf("datacenterid must be between 0 and %d", datacenteridMax-1)
}
if workerid < 0 || workerid > workeridMax {
return nil, fmt.Errorf("workerid must be between 0 and %d", workeridMax-1)
}
return &Snowflake{
timestamp: 0,
datacenterid: datacenterid,
workerid: workerid,
sequence: 0,
}, nil
}
func (s *Snowflake) NextVal() int64 {
s.Lock()
now := time.Now().UnixNano() / 1000000
if s.timestamp == now {
// 当同一时间戳(精度:毫秒)下多次生成id会增加序列号
s.sequence = (s.sequence + 1) & sequenceMask
if s.sequence == 0 {
// 如果当前序列超出12bit长度,则需要等待下一毫秒
// 下一毫秒将使用sequence:0
for now <= s.timestamp {
now = time.Now().UnixNano() / 1000000
}
}
} else {
// 不同时间戳(精度:毫秒)下直接使用序列号:0
s.sequence = 0
}
t := now - epoch
if t > timestampMax {
s.Unlock()
log.Printf("epoch must be between 0 and %d", timestampMax-1)
return 0
}
s.timestamp = now
r := int64((t)<<timestampShift | (s.datacenterid << datacenteridShift) | (s.workerid << workeridShift) | (s.sequence))
s.Unlock()
return r
}

执行结果:

image.png](https://cdn.nlark.com/yuque/0/2020/png/160940/1597399372368-33c815dd-15ce-4818-9918-b9a73d280782.png#align=left&display=inline&height=368&margin=%5Bobject%20Object%5D&name=image.png&originHeight=368&originWidth=1454&size=88323&status=done&style=none&width=1454)<br />![test.png
image.png](https://cdn.nlark.com/yuque/0/2020/png/160940/1597399372368-33c815dd-15ce-4818-9918-b9a73d280782.png#align=left&display=inline&height=368&margin=%5Bobject%20Object%5D&name=image.png&originHeight=368&originWidth=1454&size=88323&status=done&style=none&width=1454)
![test.png

大公司 snowflake 使用

百度UidGenerator、美团 Leaf-snowflake 等
https://tech.meituan.com/2017/04/21/mt-leaf.html