
咨询各位大佬们一个问题目前有两台服务器负载,使用 apache 的 SnowflakeShardingKeyGenerator 生成雪花算法作为 id ,业务上需要生成的 id 是递增的。 之前两台服务器的 SnowflakeShardingKeyGenerator 的 workId 都是默认的,高并发情况下,两台服务器的时间可能会有误差 就会导致生成的 id 是重复的。但是两台服务器根据不同的 workId 去生成虽然能解决重复的问题,但是会导致生成的 id 不是连续递增的。 有什么其他的方式实现吗(排过坑的[旺柴])。
1 my3157 2023-07-25 14:57:16 +08:00 用一个专门的服务批量生成 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 有没有考虑过最简单的方案,使用一个大数,一台往上递增,一台往下递增 |
10 luciankaltz 2023-07-25 15:34:57 +08:00 > 两台服务器的时间可能会有误差 就会导致生成的 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 想了半天为什么这东西能重复,一仔细看帖子原来没改 workerId... 你如果非想要连续递增的 d ,那么只能考虑做个服务专门搞这种自增 是不是某些架构设计出问题了,感觉不应该有这种问题.. |
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 把 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 |
@jiobanma machine id 如果都一样,那还设计这个位干嘛呢。 |
28 opengps 2023-07-25 16:04:50 +08:00 “ 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 美团开源过分布式 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 @dddd1919 热点 key 警告( |
47 jiobanma OP @luciankaltz #45 了解的有点少,想问下热点 key 可能会导致哪些问题 |
span class="no">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 一言难尽 说不懂吧,又用上了雪花 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. 给定正确的 workerId , 2. 雪花算法中将代表时间的段放在高位 |
61 xrzxrzxrz 2023-07-25 20:29:38 +08:00 跟 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 如果你能找到这样一个不依赖中心化服务、原子钟 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 、还是自身内存都可以。 你要解决的问题就是高可用。 |
span class="no">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 |