ID 是数据库的重要组成部分,但是随着分布式服务的流行传统的自增主键已经无法满足我们的需求。
一种全局唯一的 ID 变得不可或缺。
传统方案
通常来说我们会用自增 ID 或 UUID 作为我们的数据库主键。这类选择有什么好处与坏处呢?
自增 ID
如 Mysql 的 Increment ID 或 Postgresql 的 serial 都是典型的自增 ID。
优势:
数据库自动生成,性能好,使用简单,便于理解,直接表示当前数据序号。
整型数据结构,占用空间小,索引性能好。
单调递增,顺序存放。
劣势:
当 ID 被暴露出来时,容易猜测数据增量及总量。
当数据库需要拆分合并时容易出现重复。
分布式系统中,全局无法做到唯一。
对于防止猜测数据基数还有一种方法是,ID 不从1开始,而是从一个很大的随机数开始,如:5201314、20180808… 当然这种方法并不能防止他人猜测数据增量。
简单来说就是使用简单、方便,但是对分布式不友好。
UUID
因为自增 ID 的这些缺陷,诞生了 UUID(Universally Unique Identifier)。
UUID 大概长这个样:
673246fa-9b8a-480d-85c3-e7878aa441b4
其中第三段的第一个数字表示版本,这个 UUID 是 v4。
优势:
达成全局唯一。
生成简单(如 Postgresql 甚至内部支持 uuid 类型),使用方便。
完全无序,基本不用担心 ID 暴露等问题。
劣势:
长度过长,占用 16 字节,36 字符长度,影响空间使用。特别对创建索引。
无序,导致数据不可读。
影响插入速度和查询速度。
总结来就是做到了全局唯一,但是占用空间大,影响性能(实际性能没做比较不知道,但是占空间大是真的。)
其他方案
综上所述的问题就会发现,我们在分布式系统或者说微服务中需要的 ID 要满足以下特征:
必须是全局唯一的,不会出现重复现象。
最好是可排序的,方便各种操作。
最好是有时间顺序的,可读性好,
不会因为暴露 ID 而担心数据泄漏。
使用简单,短而精悍。
基于 Go 语言的替代方案不少,如:
这次主要介绍 xid 和 snowflake。
xid
xid 是一个全球唯一的 ID 生成库,xid 使用 Mongo Object ID 算法生成,并使用不同的序列化确保转换称字符串时足够短。
xid 结构如下:
4字节:值表示自 Unix 时间以来的秒数,
3字节:机器标识符,
2字节:进程ID,
3字节:计数器,以随机值开始。
他看起来是这样的:
9m4e2mr0ui3e8a215n4g
安装
$ go get github.com/rs/xid
使用
guid := xid.New()
println(guid.String())
信息拆分:
guid.Machine()
guid.Pid()
guid.Time()
guid.Counter()
xid Github地址如下: xid
Snowflake
Snowflake,也叫雪花算法,是 Twitter 提出的一种分布式自增 ID 算法,用以生成推文 ID。
snowflake 大概长这样:
1166604700124975104
下面截取一个 NBA 推文地址:
https://twitter.com/NBA/status/1166266438547427330
会发现他们的 ID 是纯数字的。那是因为 snowflake 结构如下:
41bit:时间戳段,使用毫秒单位,那么 2 的 41 次方相当于 25452 天,约 70 年。也就是可以用到2139年。
工作机器ID:这部分是需要用户自己填的,他占了 10 比特,也就是 1024 个数 0~1023。
序列号:这个字段用来标注同一毫秒内产生多个 ID 的情况。这一字段 12 比特,也就是每毫秒 4096 个 ID,一秒钟就是 409.6 万个 ID 应该够用了。
以下是 Twitter 默认格式。
+--------------------------------------------------------------------------+
| 1 Bit Unused | 41 Bit Timestamp | 10 Bit NodeID | 12 Bit Sequence ID |
+--------------------------------------------------------------------------+
Slowflake 的算法是公开的,所以任何人都可以使用这套算法生成自己的雪花算法。网上有很多种实现,有些实现还会根据自身需求修改字段长度。
下面以其中一种比较标准的实现库来说明:
安装
$ go get github.com/bwmarrin/snowflake
使用
package main
import (
"fmt"
"github.com/bwmarrin/snowflake"
)
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()
// Print out the ID in a few different ways.
fmt.Printf("Int64 ID: %d\n", id)
fmt.Printf("String ID: %s\n", id)
fmt.Printf("Base2 ID: %s\n", id.Base2())
fmt.Printf("Base64 ID: %s\n", id.Base64())
// Print out the ID's timestamp
fmt.Printf("ID Time : %d\n", id.Time())
// Print out the ID's node number
fmt.Printf("ID Node : %d\n", id.Node())
// Print out the ID's sequence number
fmt.Printf("ID Step : %d\n", id.Step())
// Generate and print, all in one.
fmt.Printf("ID : %d\n", node.Generate().Int64())
}
go snowflake Github 地址: Snowflake
总结
自增 ID 因其数据库默认、使用方便、直观、性能好等优点,依然是使用很广泛数据表的主键。
如果对数据总量和增量比较敏感或需要全局唯一,UUID 依然是比较常用的选择。
如果觉得 UUID 太大、无序、性能慢,想要用于分布式切数据生成频繁,Snowflake 或 xid 将是不错的选择。