分布式ID之雪花算法
什么是雪花算法?
Twitter开源的分布式ID生成算法,性能高,并且在单机上有序递增,使用简单、方便!它的核心是分布式以及唯一!
提到唯一ID常用的有几种方式:UUID、数据自增主键、Redis的Incr命令等
- UUID:优点是代码简单,性能比较好。缺点是没有排序,无法保证按序递增;其次是太长了比较长,存储数据库占用空间比较大,不利于检索和排序。
- 数据库自增ID:容易实现单调递增的唯一ID 的方法,并且它也方便排序和索引。但是缺点也很明显,由于过度依赖数据库,那么受限于数据库的性能会导致并发性并不高;再来就是如果数据量太大那么会给分库分表会带来问题;并且如果数据库宕机了,那么这个功能是无法使用的。
- Redis:在 Redis 中有两个命令 Incr、IncrBy ,因为Redis是单线程的所以通过这两个指令可以能保证原子性从而达到生成唯一值的目标,并且性能也很好。但是在 Redis 中,即使有 AOF 和 RDB ,但是依然会存在数据丢失,有可能会造成ID重复;再来就是需要依赖 Redis ,如果它不稳定,那么会影响 ID 生成。
分布式ID的特点:
- 全局唯一性:不能出现有重复的ID标识,这是基本要求!
- 递增性:确保生成ID对于用户或业务是递增的!区别与UUID
- 高可用性:在高并发的环境下依然表现良好!
- 高可能性:确保任何时候都能生成正确的ID
应用场景:
比如我们数据库进行分库的时候,就要保证每一个ID都是唯一的,要不然在多个库采用ID的自增方式就会出现多个ID相同,导致数据混乱了!还有在电商促销时短时间内会有大量的订单诵入到系统,比如每秒10W+;明星出轨时微博短时间内会产生大量的相关微博转发和评论消息。在这些业务场景下将数据插入数据库之前,我们需要给这些订单和消息先分配一个唯一D,然后再保存到数据库中。对这个id的要求是希望其中能带有一些时间信息,这样即使我们后端的系统对消息进行了分库分表,也能够以时间顺序对这些消息进行排序。
算法具体介绍:
- 第一位:占用1bit,其值始终是0,没有实际的作用!
- 时间戳:占用41bit,单位是毫秒,总共可以容纳约69年的时间。但是时间不会从1970年开始计算的,如果是这样的哇系统跑到2039/9/7 23:47:35就不能了!所以这里的时间戳只是相对于某个时间的增量,比如可以在系统上线是2022-01-01,那么我们完全可以把这个时间戳当做2022-07-02 00:00:00.000的偏移量!
- 工作机器id:占用10bit,可以全部用作机器ID,也可以用来标识机房ID + 机器ID,10位最多可以表示1024台机器!
- 序列号:占用12bit,用来记录同毫秒内产生不同的id。每个节点每毫秒0开始不断累加,最多可以累加到4095,同一毫秒一共可以产生4096个id
同一毫秒可以生成ID数量=1024x4096=4194304
go使用雪花算法:
可以Github看详细的文档:bwmarrin/snowflake: A simple to use Go (golang) package to generate or parse Twitter snowflake IDs (github.com)
package snowflake
import (
"time"
sf "github.com/bwmarrin/snowflake"
)
var node *sf.Node
// Init 进行时间戳的初始化
func Init(startTime string, machineID int64) (err error) {
var st time.Time
st, err = time.Parse("2006-01-02", startTime)
if err != nil {
return
}
sf.Epoch = st.UnixNano() / 1000000
node, err = sf.NewNode(machineID)
return
}
// GenID 获取id
func GenID() int64 {
return node.Generate().Int64()
}