探究分布式全局唯一Id生成器

分布式全局唯一Id生成器有啥用?

首先我们有一个共识,那就是我们的业务系统大量用到唯一 Id,比如电商系统的订单、内容社区的帖子等等。在大部分传统场景中,我们依赖 Mysql 存储数据,并依靠 Mysql 的自增 Id 来获得一个唯一 Id。
如果某个场景我们无法依赖 Mysql 的自增 Id(比如分库分表),亦或者使用 Mysql 很浪费性能(使用 Mysql 不是为了存储数据,仅仅是为了获得一个唯一 Id),那么此时使用分布式全局唯一 Id 生成器是一个合适的方案。

有哪些分布式全局唯一Id生成方式?

UUID

UUID 全称:Universally Unique Identifier。标准型式包含 32 个 16 进制数字,以连字号分为五段,形式为 8-4-4-4-12 的 36 个字符,示例:9628f6e9-70ca-45aa-9f7c-77afe0d26e05。

优点:

  1. 代码实现简单。
  2. 本机生成,几乎没有性能问题。

缺点:

  1. 无法保证递增。
  2. UUID 是字符串,存储和查询相比于整形更麻烦(需要的存储空间大、不好索引等)。

拓展:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。 不过我们这里仅讨论 UUID 作为唯一 Id 的场景,所以不能算作缺点。

应用场景:

  1. 生成 token 令牌的场景。
  2. 链路追踪场景。
  3. 不适用要求 Id 趋势递增的场景。
  4. 不适合作为高性能需求的场景下的数据库主键。

Mysql && Redis 做发号器

Mysql 与 Redis 的原理类似,都是利用数字自增来实现。

优点:

  1. 整型,且从 0 开始。
  2. 单调递增(区别趋势递增)。
  3. 查询效率高。
  4. 可读性好。

缺点:

  1. 存在单点问题,严重依赖 Mysql or Redis。
  2. 数据库压力大,高并发抗不住。
  3. 主从 Redis 存在同步延迟。(这一点请教了公司团队的大佬)
  4. 即便使用集群 Redis,仍无法避免 Redis 单点。(这一点请教了公司团队的大佬)

SnowFlake

SnowFlake 是 twitter 开源的分布式 ID 生成算法,也叫雪花算法,基于时间戳来实现。
这是 Id 的格式,一共 64 位(对应 Go 的 int64 类型、Java 的 long 类型)。

+--------------------------------------------------------------------------+
| 1 Bit Unused | 41 Bit Timestamp |  10 Bit NodeID  |   12 Bit Sequence ID |
+--------------------------------------------------------------------------+

每次生成 ID 时:

  • 第 1 位废弃不用。
  • 使用 41 位 ID 存储毫秒精度的时间戳。
  • 然后添加 10 位 NodeID。
  • 最后添加 12 位序列号,从 0 开始,并为在同一毫秒内生成的每个 ID 递增。如果在同一毫秒内生成足够多的 ID 以使序列翻转或溢出,则生成函数将暂停到下一毫秒。

业界有很多轮子,比如 https://github.com/bwmarrin/snowflake 使用 Golang 实现了雪花算法。
这是使用该库的例子:可以看到,我们需要在 NewNode 填入一个整数(因为NodeID有10位,所以该整数应该 < 1024)

func main() {

    // Create a new Node with a Node number of 1
    node, err := snowflake.NewNode(1)
    if err != nil {
        fmt.Println(err)
        return
    }

    // Generate a snowflake ID.
    id := node.Generate()
}

优点:

  1. 整型。
  2. 趋势递增。
  3. 查询效率高。
  4. 本地生成 Id,几乎没有性能问题。

缺点:

  1. 虽然是整型,但是生成的 Id 位 int64 类型(不能从 0 开始自增),需要考虑与现有业务的兼容性。
  2. 无法单调递增。
  3. 基于时间戳实现,需要考虑“时间回拨”问题。

时间回拨:雪花算法生成 Id 时,依赖机器时间,如果机器时间出现大范围回撤,那么生成的 Id 会重复。

总结

分布式全局唯一 Id 生成器作为微服务系统的基础组件,高性能高可用是他最基本的要求。
首先,UUID 生成的 Id 是字符串,无法替代传统的 Mysql 自增 Id,使用场景有限。
而无论是 Mysql、Redis 还是雪花算法,它们都存在各自的问题,后面我会结合国内几个比较有名的开源实现(百度 UId - Generator、美团 Leaf、滴滴 TinyId),聊聊业界大厂是如何提升 Mysql 发号器的性能的,是如何避免雪花算法“时间回拨”的。