看了雪花算法,服务需要配置标识位(数据中心 ID 、机器 ID ),多一项配置就多一份出错的风险,同时在不依赖其他基础服务的情况下也不太好解决时间回调的问题。然后自己实现了一个简化版的,依赖于 redis 。大佬们帮忙看看有啥问题没有,感谢。
func GlobalIncrId() (int64, error) {
script := redis.NewScript(`
local key = KEYS[1]
local stamp = ARGV[1]
local newValue = redis.call("incr", key)
if newValue then
if newValue > tonumber(stamp) then
return newValue
else
local flag = redis.call("set", key, stamp)
if flag then
return stamp
end
end
end
return nil
`)
stamp := (time.Now().Unix() - 50 * 365 * 86400) << 22
ret, err := script.Run(RedisIns, []string{sredis.KEY_GLOBAL_INCR_ID}, stamp).Int64()
return ret, err
}
自增 id 带上 stamp 信息, 是为了防止 redis 的 key 丢失, 或者值被清除。stamp 的计算回拨了 50 年, 因为 32bit 的时间戳到 2038 年就溢出了。只要机器的系统时间回调以及 key 失效这两件事不同时发生, 就能保持自增性。
低 22 位用于 redis incr 自增,高位是时间信息,即 1s 内最多支持 4194304 个 id,超出了也没关系,只是会提前占用高位的时间。
用 22 位来自增是因为某些业务可能会将 id 用在 zadd 中,zadd 的 score 范围是:-(2^53) 至 +(2^53), 即时间信息可以占用 31 位, 保证 score 不会溢出。
1
GM 2021-09-17 10:18:40 +08:00 23
看到那么多发明 id 生成器的,我来分享一个我喜欢用的吧:
select uuid_short() as id; 优点: 纯数字单调递增 id 性能足够好,每秒几万 ID 毫无压力 分布式,多台服务器生成的 id 不会重复 依赖少,只要你用 MySql 就够了。 不怕 id 生成系统失效,只要数据库还活着就能用。如果数据库都挂了,id 生成器活着也没用。 |
2
zhaokun 2021-09-17 10:21:27 +08:00 1
依赖的服务要稳住
雪花可以拿机器 ip 作为机器 id,不需要配置 |
5
Ariver 2021-09-17 10:39:35 +08:00 via iPhone
redis 集群的话考虑了吗
|
8
flycloud OP |
9
bthulu 2021-09-17 12:07:00 +08:00
@zhaokun ip 重复了咋办,我司机器是分布式部署的,直接部署在门店里,门店用的都是家用路由器,ip 都是 192.168.x.xxx ,很容易重复的
|
10
enan01 2021-09-17 12:27:53 +08:00 via iPhone
redis 如果使用主从,从节点同步延迟,也会导致 ID 重复吧
|
11
XiLingHost 2021-09-17 12:32:34 +08:00
@bthulu 那就用 mac
|
12
skies457 2021-09-17 12:59:53 +08:00
return ++i;
|
13
draymonder 2021-09-17 13:16:20 +08:00
1. 用脚本,会拖慢 redis 性能,主从之间复制也可能会出问题
2. 另外,请求 redis 失败了呢,多一个依赖,就会降低整体系统稳定性 |
14
billly 2021-09-17 13:44:05 +08:00
redis 出问题的几率肯定比我加两个配置出问题的几率大
|
16
flycloud OP |
18
enan01 2021-09-17 20:14:12 +08:00
@flycloud 主节点挂掉,切换到从节点,从节点异步复制,没办法保证数据强一致,如果从节点 key 存在,但是 value 没有完成复制,从节点继续 incr,会存在重复的可能
|
19
workingonescape 2021-09-17 20:34:43 +08:00
@GM 你这个有链接么,想学习一下
|
20
GM 2021-09-17 20:43:53 +08:00
|
22
seakingii 2021-09-17 22:15:13 +08:00
最大的问题是用 REDIS
|
23
xingzhi 2021-09-18 01:21:21 +08:00
|
24
wangritian 2021-09-18 02:51:18 +08:00
数据中心 ID 配置不麻烦吧,机器 ID 可以用 redis incr 搞定啊,生成就不要依赖网络了,也可以用 k8s-statefulset 的 hostname 最后数字作为机器 ID
|
25
Rocketer 2021-09-18 03:23:59 +08:00
uuid 的问题在于多台机器之间无法实现递增关系,雪花算法主要就是为了解决这个问题的,后生成的 id 一定大于之前生成的 id 。
如果对雪花的性能不满意,可以考虑做个 id 微服务,闲余时提前生好,存在内存里,需要时取第一个发下去就行了,这样不会重复,也保证了递增关系。关键是算法可以很简单(比如就是数字递增),从而保证这个微服务有极强的承载能力。 |
26
hanxiV2EX 2021-09-18 06:22:42 +08:00 via Android
https://github.com/hanxi/skynet-demo/commit/b4ef9dd1b8e835318da8f17bec885bb4b58d5b73
我也实现了一个雪花算法,分布式不依赖数据库。用本地定时存文件的方式解决时间回调问题。 |
27
hanxiV2EX 2021-09-18 06:34:29 +08:00 via Android
既然用 redis 了,还不如直接用 lua 实现集成在 redis 里,用 EVALSHA 获取 id
|
29
JamesChen 2021-09-18 08:36:09 +08:00
路过提一嘴,“if newValue then”和“if flag then”永远都是 True 。lua 不是 js,别写混了
|
31
flycloud OP @JamesChen #29 谢谢,看了文档,确实啊。如果 redis.call 命令有错误会直接抛出异常了,如果执行成功,返回值一定是 True
|
32
egfegdfr 2021-09-18 18:30:45 +08:00
mybatis-plus 自带的 ID_WORKER 及挺好用的,不需要太多配置,直接注解就行,生产的是 有序但是不连续的 id,还可防止被人恶意爆破的问题
|
33
cp19890714 2021-12-13 22:02:09 +08:00
1. 中心化代替分布式
雪花算法三要素:1 时间戳 2 自增序列 3 全局唯一标识 “全局唯一标识” 实现了分布式能力, 而你的算法删掉了这个要素,用中心化的 redis 替代了。 2.多依赖代替单依赖 雪花算法依赖 服务器时间的同步性。 你的算法依赖 网络和 redis 。 雪花算法仅在启动的时候 workId 依赖 Redis, 而你的是时刻都依赖 redis 。 总结:删掉对服务器时间的依赖,却大大降低了高可用性。性能也肯定降了一大截。 |