在应用程序中,经常需要全局唯一的ID作为数据库主键。在一台节点容易全局唯一,那在多台节点呢?
有两个思路:
- 1使用散列函数,如sha256,加上时间戳、mac地址、cpu负荷、随机数等组成,id足够长,引入多个不确定因素,以至于碰撞几率非常小,可以认为是全局唯一。例如uuid就是这种。但是uuid是字符串的形式,对于DB来说,占用的空间至少大一倍,DB的索引是需要存储和对比的,因此在存储空间和查询时间上面都比整形要低,这种情况在DB的数据条数越多时越明显。
- 2使用分割法。每个节点都保证自己生成的所有id在本机唯一,每个节点都有一个人为分配的不重复节点编号,插入id中,这样所有节点的id都是全局唯一的。
- 3利用DB自带的主键唯一性来确保id唯一。但是db的自增id是需要等到事务提交后,ID才算是有效的。有些双向引用的数据,不得不插入后再做一次更新,比较麻烦。
第二种方式是类似Twitter的Snowflake算法,它给每台机器分配一个唯一标识,然后通过时间戳+标识+自增实现全局唯一ID。这种方式好处在于ID生成算法完全是一个无状态机,无网络调用,高效可靠。缺点是如果唯一标识有重复,会造成ID冲突。
Snowflake算法采用41bit毫秒时间戳,加上10bit机器ID(最多支持1024台id服务器),加上12bit序列号,理论上最多支持1024台机器每秒生成4096000个序列号。409万个id每秒,在任何交易平台目前都是够用的。
推特的id构成(从最高位往最低位方向):
- 1位 ,不用。固定是0
- 41位 ,毫秒时间戳
- 5位 ,数据中心ID (用于对数据中心进行编码)
- 5位 ,WORKERID (用于对工作进程进行编码)
- 12位 ,序列号。用于同一毫秒产生ID的序列 (自增id)
下面是用golang实现的uuid方法(uuid类型为整形):
package main
import (
"fmt"
idworker "github.com/gitstliu/go-id-worker"
)
func main() {
currWoker := &idworker.IdWorker{}
currWoker.InitIdWorker(1000, 1)
newID, err := currWoker.NextId()
if err == nil {
fmt.Println(newID)
}
}
下载库
go get github.com/gitstliu/go-id-worker
下面是在vscode中的调试结果
API server listening at: 127.0.0.1:4442
4917572028174794752
Process exiting with code: 0
用mysql自带的自增id生成全局唯一id