前言
在我们日常进行开发的时候,不可避免的会出现对用户表的操作,而为了保证每一个用户的唯一性,这就需要我们创建一个唯一性的id
,但是现在有一个问题,如果我们仅仅像通过自增这样方式来创建唯一的id,这无疑是非常不合适的,他人可以通过自己新创建账号的id
进而大致推算出当前网站大致的用户量,这样会对网站的安全造成极大的威胁,那我们可以如何去避免这种情况呢?这就是我们今天所要介绍的内容:分布式ID生成。
分布式ID的特点及其应用
分布式ID主要具有以下特点:
全局唯一性
:不能出现有重复的ID标识递增性
:确保生成ID对于用户/业务是递增的高可用性
:确保如何情况下生成的id都正常高性能性
:在高并发的环境下依旧表现良好
而今天我们所要介绍的分布式ID生成方案是业内比较推荐的方法—— Snowflake
(雪花算法)
它的优点有以下几个:
- 生成时不依赖于数据库,完全在内存中生成(高性能高可用)
- 每秒钟能生成数百万的自增 ID(高吞吐)
- 存入数据库中,索引效率高
缺点也比较明显:
- 依赖服务器时间,服务器时间回拨时可能会生成重复 id。
雪花算法的实现机理
一.雪花算法的组成
在雪花算法中会生成64bit的long型数值,它可以分为如下四个部分:
- 固定值(符号位,0-正 1-负)
- 时间戳:41bit,存储毫秒级时间戳(41 位的长度可以使用 69 年)
- 标识位:12bit,用于表示在同一毫秒内生成的多个ID的序号。如果在同一毫秒内生成的ID超过了4096个(2的12次方),则需要等到下一毫秒再生成ID。
拓展: 虽然默认生成的位数是64位,但是这个我们可以手动调节
二.对于雪花算法部分问题的分析
1.生成ID重复问题
场景:一个订单微服务,通过雪花算法
生成 ID,共部署三个节点,标识位一致。此时有 200 并发,均匀散布三个节点,三个节点同一毫秒同一序列号下生成 ID,那么就会产生重复 ID
由此我们可以知道该问题出现的前置条件如下:
- 服务通过集群的方式部署,其中部分机器标识位一致;
- 业务存在一定的并发量,没有并发量无法触发重复问题;
- 生成 ID 的时机:同一毫秒下的序列号一致。
解决方案:
预分配:
应用上线前,统计当前服务的节点数,人工去申请标识位.(适用于服务节点固定或者项目较少)动态分配:
将标识位存放在 Redis、Zookeeper、MySQL 等中间件,在服务启动的时候去请求标识位,请求后标识位更新为下一个可用的统一分配:
将标识位存放在 Redis、Zookeeper、MySQL 等中间件,在服务启动的时候去请求标识位,请求后标识位更新为下一个可用的
2.标识位的使用方式
标识位一共10 bit,如果全部表示机器,那么可以表示1024台机器,如果拆分,5 bit 表示机房,5bit表示机房里面的机器,那么可以有32个机房,每个机房可以用32台机器。
雪花算法的实现
这里我们选择使用第三方包sonyflake
go get github.com/sony/sonyflake
package snoyflake
import (
"fmt"
"github.com/sony/sonyflake"
"time"
)
var (
sonyFlake *sonyflake.Sonyflake
sonyMachineId uint16
)
func getMachineId() (id uint16, err error) {
return sonyMachineId, nil
}
func Init(starttime string, machineId uint16) (err error) {
sonyMachineId = machineId
t, _ := time.Parse("2006-01-02", starttime) // 设置开始时间
setting := sonyflake.Settings{
StartTime: t,
MachineID: getMachineId,
}
sonyFlake = sonyflake.NewSonyflake(setting) //用配置生成sonyflake节点
return
}
// GetID 返回生成的id
func GetID() (id uint64, err error) {
if sonyFlake == nil {
err = fmt.Errorf("sonyflake not init")
return
}
return sonyFlake.NextID()
}
最后我们尝试来生成一下:
也是看到成功的生成了id,再运行一次:
可以看到生成的id并不相同,说明我们的雪花算法已经成功实现了。
结语
在原理方面参考了下面的文章,在此鸣谢大佬的分享:
一文读懂“Snowflake(雪花)”算法