咨询各位大佬们一个问题目前有两台服务器负载,使用 apache 的 SnowflakeShardingKeyGenerator 生成雪花算法作为 id ,业务上需要生成的 id 是递增的。 之前两台服务器的 SnowflakeShardingKeyGenerator 的 workId 都是默认的,高并发情况下,两台服务器的时间可能会有误差 就会导致生成的 id 是重复的。但是两台服务器根据不同的 workId 去生成虽然能解决重复的问题,但是会导致生成的 id 不是连续递增的。 有什么其他的方式实现吗(排过坑的[旺柴])。
1
my3157 2023-07-25 14:57:16 +08:00 7
用一个专门的服务批量生成 ID , 其他服务直接从这个服务拿
|
2
mineralsalt 2023-07-25 14:57:28 +08:00
没用过 SnowflakeShardingKeyGenerator, 但是如果是我实现这样的需求, 能想到 2 种办法, 第一就是 redis 加锁, 但是会影响并发效率. 第二种就是发现 id 重复时递归生成一次,直到不重复为止
|
3
mineralsalt 2023-07-25 14:59:29 +08:00
1L 这种多引入一个服务感觉太重了, 多一个服务就多一份负担和不稳定性, 不是太优雅
|
4
thevita 2023-07-25 15:25:10 +08:00
2L 提的第一个方法与 1L 本质上是一样的,都是引入一个全局一致的协调者(目前看这其实是比较现实的办法,功能简单,稳定性和性能应该能做很多优化,当然具体看你场景能否接受)
|
5
thevita 2023-07-25 15:29:04 +08:00
2L 第二种也是一个办法,相当于 用 db 作为一致性保证?,乐观冲突检测的方式来做,但是 db 的事务的貌似还是得依赖全局锁的方式来支持 insert id 有序
|
6
zhzy0077 2023-07-25 15:29:11 +08:00
2 台服务器能承载的业务直接用数据库的 auto increment 不行吗
|
7
CocaCola001 2023-07-25 15:31:19 +08:00
我觉得还是新建一个服务用来生成 id 比较好,后期也方便扩展
|
8
thevita 2023-07-25 15:31:30 +08:00
还不如看看你的需求只是要 “递增“ 呢,还是真的需要严格的全局有序
|
9
SenLief 2023-07-25 15:32:42 +08:00 3
有没有考虑过最简单的方案,使用一个大数,一台往上递增,一台往下递增
|
10
luciankaltz 2023-07-25 15:34:57 +08:00 1
> 两台服务器的时间可能会有误差 就会导致生成的 id 是重复的
snowflake 算法初始化的时候会填入一个 machine id ,为什么两台服务器上生成出来的 id 会重复呢( 还是说你故意设置成了一样的 machine id ,是希望所有生成出来的 id 在全局上单调递增并且唯一? 如果是的话那就必须引入一个单点的算号服务,不管是一个 app 服务还是 db 的自增 |
11
veike 2023-07-25 15:35:33 +08:00
ulid 可行?
|
12
Masoud2023 2023-07-25 15:36:40 +08:00 2
想了半天为什么这东西能重复,一仔细看帖子原来没改 workerId...
你如果非想要连续递增的 id ,那么只能考虑做个服务专门搞这种自增 是不是某些架构设计出问题了,感觉不应该有这种问题.. |
13
thevita 2023-07-25 15:36:49 +08:00
想到一个比较破的方案:
就是你在 db 里面 预先 “生成, 分配” 一批 id (假设这里你的 全局有序 id 是主键) 这样就能让服务来`抢` next id, 对 行加锁了,并发应该会好一些, 就是不能回滚,需要让签名的 id 失效 |
14
jiobanma OP @mineralsalt #2
@thevita #4 @CocaCola001 #7 这个比较简单 缺点就是可能会有性能问题已经服务掉线之后的问题 @luciankaltz #10 目前都是默认的 没有传参,所以你说的 machine id 应该是个默认值,两台服务器应该是一样的值 @veike #11 uuid 缺点太多了 |
15
nekolr 2023-07-25 15:40:06 +08:00
大致上是趋势递增的就可以了呀,或者像楼上几位说的,独立出来一个服务
|
16
dw2693734d 2023-07-25 15:41:20 +08:00
@jiobanma 能讲讲 uuid 缺点吗
|
17
bugmakerxs 2023-07-25 15:41:57 +08:00
https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.md
为什么要连续?简单点引入这个就行 |
19
silentsky 2023-07-25 15:43:11 +08:00
很简单的事情 搜 Redisson IdGenerator
|
20
bugmakerxs 2023-07-25 15:44:27 +08:00
另外的简单方案是 timestamp + (redis.incr(key) % 100000)
服务器时钟用 ntp 同步 |
21
tabris17 2023-07-25 15:44:34 +08:00
又要严格自增,又要分布式,你这是既要又要啊
|
23
ns09005264 2023-07-25 15:45:48 +08:00 via Android 5
把 workid 放到最后试试,比如服务 1 生成序列号 123777701 服务 2 同一时间生成 123777702 ,这样不管从全局来看还是单个服务来看,id 都是连续的
|
24
nekolr 2023-07-25 15:46:29 +08:00
@dw2693734d 1 太长 2 可能的安全性问题 3 不是递增的,会导致数据存储不够紧凑,不能充分利用 B+ 树的优势
|
25
Belmode 2023-07-25 15:56:54 +08:00
修改一下 workerId ,你这问题不是迎刃而解了
|
26
jiobanma OP |
27
crysislinux 2023-07-25 16:01:30 +08:00 via Android
@jiobanma machine id 如果都一样,那还设计这个位干嘛呢。
|
28
opengps 2023-07-25 16:04:50 +08:00 1
“ id 是递增”天然的设计优势就是自增步长设置为 2 啊,一台 1 作为种子,一台 2 作为种子
|
29
yangyaofei 2023-07-25 16:05:05 +08:00
给机器分配 ID(0 到 N 的自然数)不就好了, 比如 10 台, 就找个 N=11, 每个机器自己生成的时候乘以 11 加上自己的 ID 就好了, N 可以有个比较大的余量, 虽然很工程但是感觉应该挺好用
|
30
qingshengwen 2023-07-25 16:06:11 +08:00
没太明白,时间戳是在最前面的,这样一定是可以做到大致递增的,跟 workId 有什么关系呢
|
31
fuis 2023-07-25 16:06:15 +08:00
为什么需要严格的单调递增?
|
32
icedir 2023-07-25 16:07:24 +08:00
了解一下美团的 leaf ,解决了这个场景
|
33
cat 2023-07-25 16:14:25 +08:00
或许可以看看这个:
Sharding & IDs at Instagram https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c |
34
leonshaw 2023-07-25 16:18:13 +08:00
这种需求不管怎么折腾都等效于一个单点
|
36
bringyou 2023-07-25 16:25:41 +08:00 1
美团开源过分布式 ID 的生成服务,并有写文章介绍,可以参考: https://tech.meituan.com/2019/03/07/open-source-project-leaf.html
|
37
xiaoHuaJia 2023-07-25 16:26:56 +08:00
uuid 重写加入时间,可以实现是有排序的。目前我们系统的方案就是这个。除了 id 看起没没纯数字那么规整,差一点点性能,其它都是小问题
|
38
yc8332 2023-07-25 16:32:12 +08:00
写个服务预先生成好,放 redis 或者自己的服务。然后用的直接取就行了。。
|
39
yaodong0126 2023-07-25 16:34:46 +08:00
@mineralsalt 我倒是觉得 1L 非常优雅
|
40
PythonYXY 2023-07-25 16:36:55 +08:00
需要递增就没法采用分布式的方式,无论是 snowflake 还是基于数据库自增 id ,最后肯定还是会有单点问题。
|
41
dddd1919 2023-07-25 16:39:43 +08:00
所有服务连同一个 redis ,使用同一个 key 的 incr 生成 id ,既能自增又分布式
|
42
mineralsalt 2023-07-25 16:51:20 +08:00
@yaodong0126 #39 多运行一个服务和使用 redis 加锁没有本质区别, 一般后端项目都会引入 redis, 所以几乎是没有额外成本的. 另外我简单说一下多运行一个服务的缺点.
1. 浪费内存, java 后端项目启动就几百兆 2. 稳定性降低, 本来他是分布式部署的项目强行被这个单点服务拖累了, 一旦这个服务挂掉, 其他服务都 GG 3. 开发任务多了, 多维护一个模块, 也要多上线一个实例 当然这是一个好的解决方案, 但是一个小项目, 我还是秉承着资源最小化的理念, 不要过多的引入服务和模块 |
43
wendellup2018 2023-07-25 17:03:25 +08:00
@mineralsalt 还是有区别的, 每个项目都引入 redis, 连接数要爆掉了。引入项目可以增加网关控制调用。
|
44
quantal 2023-07-25 17:04:14 +08:00
直接用 ULID 吧,生成的 id 带时间戳精确到毫秒,毫秒级有序
|
45
luciankaltz 2023-07-25 17:06:21 +08:00 1
@dddd1919 热点 key 警告(
|
47
jiobanma OP @luciankaltz #45 了解的有点少,想问下热点 key 可能会导致哪些问题
|
49
jiobanma OP @qingshengwen #30
public static Comparable<?> geneSnowFlakeIDByWorkId(String workId) { shardingKeyGenerator.getProperties().setProperty("worker.id", workId); return shardingKeyGenerator.generateKey(); } public static void main(String[] args) { System.out.println(geneSnowFlakeIDByWorkId("100")); // 890660284086501376 System.out.println(geneSnowFlakeIDByWorkId("200")); // 890660284095299585 System.out.println(geneSnowFlakeIDByWorkId("100")); // 890660284094889986 System.out.println(geneSnowFlakeIDByWorkId("200")); // 890660284095299587 } 这样是不就理解了 |
50
wangxiaoaer 2023-07-25 18:07:51 +08:00 via iPhone
也没有大佬解释下 1 楼的方案?目前两台雪花算法在高并发下会重复,说明业务并发还是不低的,再加一个专门生成 id 的服务,是不是还是调用雪花服务器?如果这样的话这个服务就会面临瓶颈,服务如果再通过集群搞成分布式,感觉会面临同样的问题啊。
|
51
joesonw 2023-07-25 18:23:24 +08:00 via iPhone
|
52
lovelylain 2023-07-25 18:23:26 +08:00 via Android
@jiobanma 自己实现,时间戳放最高位,自增数放中间,workid 放最低位
|
53
hsymlg 2023-07-25 18:42:09 +08:00
单调递增和趋势递增是 2 个概念,1L 其实就是解决方案,再起一个服务专门做 id 生成,另外其他楼说的美团的 leaf ,那个玩意儿的基于号段的和雪花的也是 2 种不同场景,要注意甄别
|
54
IDAEngine 2023-07-25 18:49:25 +08:00
@wangxiaoaer 他这个两台服务器的 machine id 用的同一个,当然有重复的可能性。
|
55
luciankaltz 2023-07-25 19:04:01 +08:00 via Android
@jiobanma 想象一下你对一张表的数据行做 10 次 update 。如果是 10 条记录,那么这些操作之间不会有并发锁问题;如果 10 次更新的是一条记录,那么 10 次操作会退化成串行执行
热点问题会直接导致各种分片或者分布策略失效,大多数情况下直接退化到单机性能(甚至可能影响到其他的操作 |
56
xiangyuecn 2023-07-25 19:28:58 +08:00 2
一言难尽
说不懂吧,又用上了雪花 id 算法 说懂吧,又不给服务器分配唯一编号🐶 |
57
leonshaw 2023-07-25 19:37:32 +08:00
不知道你业务顺序是怎么定义的?
两个不相关的客户端几乎同时发送的请求本身就是没有顺序的,网络随便抖一下到服务器的顺序就变了,更不要说还有相对论了。技术上讲,事件先后只有在同一个点比较才有意义,你分 ID 的时候就是要保证和这个顺序一致。比如来自同一个连接的请求在同一个线程处理。 |
58
sampeng 2023-07-25 19:54:17 +08:00 via iPhone
lz 始终没解释为啥一定要递增。
产品需求?只有疯了的产品才关注这个。leader 的需求?那是技术不太行… |
59
dayudayupao 2023-07-25 19:57:58 +08:00
@veike 万万不可,会降低 b 树的效率,降低 pagecache 的利用率
|
60
chenluo0429 2023-07-25 20:21:18 +08:00 via Android 1
1. 给定正确的 workerId ,
2. 雪花算法中将代表时间的段放在高位 |
61
xrzxrzxrz 2023-07-25 20:29:38 +08:00 1
跟 23L 说的一样。把 workId 放在 ID 的尾部,时间日期放在前面,这样两个机器生成出来的 id 就是近似连续递增的(当然没法做到绝对递增,要绝对,只能有一个中间服务处理)。例如,{yyyyymmdd} + {workId} + {Hms}。(自己写个雪花算法原理的 id 生成器)
|
62
veike 2023-07-25 20:31:52 +08:00 via Android
@dayudayupao 能见降低多少啊
|
63
Macrow 2023-07-25 21:05:42 +08:00
https://github.com/rs/xid
零配置,可排序 Features: Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake Base32 hex encoded by default (20 chars when transported as printable string, still sortable) Non configured, you don't need set a unique machine and/or data center id K-ordered Embedded time with 1 second precision Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process Lock-free (i.e.: unlike UUIDv1 and v2) Name Binary Size String Size Features UUID 16 bytes 36 chars configuration free, not sortable shortuuid 16 bytes 22 chars configuration free, not sortable Snowflake 8 bytes up to 20 chars needs machine/DC configuration, needs central server, sortable MongoID 12 bytes 24 chars configuration free, sortable xid 12 bytes 20 chars configuration free, sortable |
64
iwdmb 2023-07-25 21:11:53 +08:00
如果没有中心化服务的话
没有这种可以严格递增的方法 不然 Google 就不用搞原子钟了 |
65
iwdmb 2023-07-25 21:17:16 +08:00 1
如果你能找到这样一个不依赖中心化服务、原子钟 GPS 硬体的方法
去面试 Google 一定会上 可以帮他们数据中心节省大量的成本 |
66
iwdmb 2023-07-25 21:18:06 +08:00
美团的 Leaf 只有保证整体趋势递增 不保证严格递增
UUID 有重复的可能 所以也不是严格递增 |
67
iwdmb 2023-07-25 21:24:32 +08:00
分布式严格递增
这个问题目前最简单、经济、性能最好的解法就是中心化服务 信我一把 |
68
mrjnamei 2023-07-25 21:56:54 +08:00
你服务是分布式的,要求每个服务生成的 id 必须是全局自增,因此必须引入一个新的组件来通信,不然无法得知当前服务器生成的 id 是否是递增的?
如果需要引入一个组件就比较好办了,高并发情况考虑高可用就行,高并发是指特别高的并发,假设服务 idGen 服务已经满足高可用的前提下,生成自增 id 显然不是问题,不管使用 redis 、还是自身内存都可以。 你要解决的问题就是高可用。 |
69
cosmic 2023-07-25 22:49:48 +08:00
用 etcd cluster 实现一个全局唯一且连续递增的 sequence generator,确保 consistency 和 availability. 然后服务器每次请求获取一个 batch 的 id,然后服务器每次 assign 一个,当一个 batch 用完后,向 sequence generator service 请求下一个 batch
|
70
tangtj 2023-07-25 22:53:04 +08:00
雪花算法是有预留,数据中心 id, 机器 id 的位置. 你没改那就需要调整配置.使他们不一样. 这样就不会出现完全一样的.
|
71
JohnChang 2023-07-25 23:09:43 +08:00
V 站到底多少人用这个头像啊?我怀疑是不是都抄我的
|
72
walkerliu 2023-07-25 23:21:23 +08:00
什么业务哇需要这么精确严格的递增吗,snowflakeID 前 41 个 bit 是 timestamp ,都能精确到毫秒了 。我觉得不如倒退需求是否需要那么精确的递增
|
73
conn457567 2023-07-25 23:23:04 +08:00 via Android
雪花算法的前提条件就是每个实例的 workerid 不能相同啊,可以用分布式协调中间件 zookeper 给每个实例分唯一 id ,或者简单点用 redis 来实现给每个实例分配唯一 id
|
74
L0L 2023-07-25 23:26:22 +08:00
高并发的场景下,雪花的递增不太可靠吧?最好说一下一定要递增的业务场景把,看看能不能换个角度解决这个问题?既然上了分布式 ID 了,尽量就避免用其作为业务含义上排序的用途吧,看看是否可以用时间戳之类的。否则 1 楼说的那个需要单独启动一个序列号服务,感觉如果只有两台服务器的话,代价反而有点高,不经济。
|
75
jdOY 2023-07-26 02:12:59 +08:00
就两台服务器搞这么复杂,服务器做一下时间同步,改一下生成雪花的几个参数就行了
|
76
ryd994 2023-07-26 04:00:28 +08:00 via Android
snowflak 本来就不保证是完全单调递增,而是保证 ID 大体上是单调连续递增,但是允许小规模,短时间的无序
|
77
xuanbg 2023-07-26 06:25:39 +08:00
分布式系统你是很难保证严格按照时间同步递增的,保证单机单调递增就够了。
|
78
franklu 2023-07-26 08:06:21 +08:00
我想问一下,在分布式下,你们怎么测试确保 id 是不重复的,要是有成熟测试方案,我觉得写代码会更简单。
|
79
franklu 2023-07-26 08:07:05 +08:00
还是说只能用思维模型测试,只能理论上推理出没问题。
|
80
trzzzz 2023-07-26 08:34:09 +08:00 via iPhone
为啥不用数据库的自增 id 呢
|
81
wxd21020 2023-07-26 08:42:46 +08:00
借楼,请教大佬们,如何生成一个 10-13 位的唯一 id ,目前是通过雪花算法生成 id 后,将 id 顺序调转,然后一直循环除以 10 ,最终的到一个 10-13 位的数字,经过简单的并发测试都能是唯一的,不知道时间长了会不会有不唯一的值。
|
82
msg7086 2023-07-26 08:48:49 +08:00
全局层面上做成递增,那势必一台机器要依赖另一台机器产生的 ID ,那就没办法做并发。
|
83
jiobanma OP @xiangyuecn #56 认真审题好吗? 分配唯一编号现在会出现不是递增的。
|
86
wqhui 2023-07-26 09:25:55 +08:00
@wxd21020 雪花是时间戳+机器唯一标识+序列号,同一节点同一时间戳生成时序列号会递增,同一节点的情况下需要截取时间戳跟序列号才能保证唯一,你这种截取方式并发高点就冲突了
|
88
knightgao2 2023-07-26 09:42:39 +08:00
雪花算法多台机器也可以递增呀,只要机器的时钟对的就行
|
89
zpf124 2023-07-26 09:52:59 +08:00
非要保证递增,那就只能使用锁或者单独的 id 生成服务,以并发的代价来保证 id 生成同一时间唯一。
|
90
lmmlwen 2023-07-26 09:56:15 +08:00
你们业务并发多大啊?
|
91
KickAssTonight 2023-07-26 09:57:43 +08:00
两个机器时钟不一致,那应该必然会出现不递增的情况吧
|
92
leonshaw 2023-07-26 10:00:00 +08:00
问题的核心就是为什么需要递增,被你一句话带过去了?
|
93
vanityfairn 2023-07-26 10:13:38 +08:00
专门的服务批量生成 ID ,最通行,最常用
|
94
zhh0000zhh 2023-07-26 10:22:23 +08:00
有一次面阿里的时候面试题就是要求实现分布式单调递增主键生成,必须没有仲裁来实现,我不会,被阿里面试官鄙视了,最后我问他,他没告我咋弄。
mark 一下看看有没有人能搞出来,我至今觉得面试官实际上是个糊涂蛋 |
95
xiangyuecn 2023-07-26 10:26:47 +08:00
@jiobanma #84 我在#56 讲的是“ id 是重复的”这个问题
只要你单个节点里面,时间不倒流,雪花算法是不可能产生重复 id 的,每个节点都要有唯一编号,最多 1024 个节点,这个是保证全局不重复的基础。 雪花算法的时间精度是 1 毫秒,单个节点 1 毫秒内最多产生 4096 个 id (会加锁串行生成),单节点只要 1 毫秒内超过了 4096 个就会 sleep 等待下 1 毫秒再分配 id ,你开了 2 个节点,那就 1 毫秒能产生 4096*2=8192 个 id ,1 毫秒里面这些 id 只能通过节点 id 来区分保证不重复 单个节点内生成的 id 因为是串行生成的因此是完全有序的,但同 1 毫秒内多个服务器生成的 id 就不能保证有序了,1 秒内看这 1000 毫秒生成的 id 又是有序的,这叫时间上粗略有序 “id 碰撞” “重复” 每个节点分配了唯一编号,只要你不乱改服务器时间时间怎么可能出现这种情况 |
96
gundam0603 2023-07-26 10:40:23 +08:00
雪花的 id 本来就不是连续的啊 ,按理说改下 workId 就行
|
97
Aresxue 2023-07-26 10:56:54 +08:00
1.改下算法把 workId 放到 id 尾部(不依赖外部服务和中间件);
2.使用 redis 自增序列(大多数项目已经引入 redis ,只有代码改造成本); 3.号段 使用序列表按照 size 取号段到内存(更稳健的模式但改造成本大,且只有趋势递增失去了单调递增性); 4.引入新服务(对原有服务耦合小,新增永远比修改更安全,但维护成本拉满); 5.使用数据库代理层中间件(如果已经有了算是性价比最高的方案,不然就没有操作意义); 顺便一提如果集群大了时钟同步是个麻烦的事情,基本上总是会有时钟回拨,对雪花算法有一定的影响,但本身也有一定措施缓解这个情况,如使用缓存的 id 等,还是要早做考虑。 |
98
cheng6563 2023-07-26 11:02:46 +08:00
没得治,找个单独的组件生成 ID ,并且还得牺牲可用性。
|
99
iamfenges 2023-07-26 11:14:41 +08:00
workId 放 id 的最后面会有什么问题吗
|
100
daye 2023-07-26 11:15:32 +08:00
把大家的回复都看了,提供一个新思路给 OP ,可以通过雪花算法 + Redis 的 increment 命令来实现,能保证在两台服务器生成的 ID 不重复、绝对的递增,那么 ID 里的时间戳值就不是真实的生成时间(雪花 id 优点之一)
基本原理是: 1. 判断 Redis 的 ID_KEY 是否存在,若不存在,则加分布式锁通过雪花算法生成 ID 设值 2. 对 ID_KEY 进行 increment 命令获取递增后的值作为 ID |