分布式序列号生成?

Distributed sequence number generation?

提问人:Jonathan Holloway 提问时间:4/20/2010 最后编辑:Lorenzo BelliJonathan Holloway 更新时间:1/14/2022 访问量:92268

问:

过去,我通常使用数据库序列实现序列号生成

例如,使用 Postgres SERIAL 类型 http://www.neilconway.org/docs/sequences/

我很好奇如何为没有数据库的大型分布式系统生成序列号。有没有人有任何经验或建议,可以以线程安全的方式为多个客户端实现序列号生成?

Java Apache ZooKeeper 序列

评论

0赞 4/16/2011
这个问题很老了,但请看我的新答案 stackoverflow.com/questions/2671858/......
0赞 diegosasw 11/20/2018
你如何使用 nextval.org?这个网站有点奇怪,我不知道是怎么回事。是一些Unix命令吗?还是一些云服务?

答:

18赞 Steven Schlansker 4/20/2010 #1

您可以让每个节点都有一个唯一的 ID(无论如何您都可能拥有该 ID),然后将其附加到序列号之前。

例如,节点 1 生成序列 001-00001 001-00002 001-00003 等,节点 5 生成序列 005-00001 005-00002

独一无二:-)

或者,如果你想要某种集中式系统,你可以考虑让你的序列服务器分块发布。这大大减少了开销。例如,您不是从中央服务器请求必须为每个必须分配的 ID 请求新 ID,而是从中央服务器请求 10,000 个块的 ID,然后只需在用完时执行另一个网络请求。

评论

1赞 ishan 7/11/2015
我喜欢你关于批量 ID 生成的观点,但它只是限制了任何实时计算的可能性。
0赞 Janakiram 9/12/2015
我已经实施了类似的机制。因此,除了客户端缓存序列块之外,我还添加了几个缓存序列块的服务器主机。(单个)主生成器维护在一些高可用性存储或单主主机中,只能访问服务器主机群。服务器缓存也将帮助我们获得更多的正常运行时间,尽管单个主服务器会关闭一段时间。
5赞 Phil 4/20/2010 #2

为什么不使用(线程安全的)UUID 生成器?

我可能应该对此进行扩展。

UUID 保证是全局唯一的(如果您避免使用基于随机数的 UUID,其中唯一性是极有可能的)。

无论您使用多少个 UUID 生成器,每个 UUID 的全局唯一性都会满足您的“分布式”要求。

通过选择“线程安全”UUID 生成器,可以满足您的“线程安全”要求。

假定每个 UUID 的全局唯一性保证满足“序列号”要求。

请注意,许多数据库序列号实现(例如 Oracle)不能保证单调递增或(甚至)递增序列号(基于每个“连接”)。这是因为在每个连接的基础上,在“缓存”块中分配了一批连续的序列号。这保证了全球唯一性保持了足够的速度。但是,当多个连接分配时,实际分配的序列号(随着时间的推移)可能会混乱!

评论

1赞 Pavel 6/20/2015
虽然 UUID 可以工作,但它们的问题在于,如果您最终需要为生成的密钥编制索引,则必须小心如何存储它们。它们通常也会比单调增加的序列占用更多的空间。有关使用 MySQL 存储它们的讨论,请参阅 percona.com/blog/2014/12/19/store-uuid-optimized-way
8赞 wsorenson 4/20/2010 #3

如果它真的必须是全局顺序的,而不仅仅是唯一的,那么我会考虑创建一个单一的、简单的服务来分配这些数字。

分布式系统依赖于许多小服务的交互,对于这种简单的任务,你真的需要还是真的会从其他复杂的分布式解决方案中受益?

评论

3赞 Navin 8/9/2018
...当运行该服务的服务器出现故障时会发生什么?
1赞 nic ferrier 1/1/2019
有提醒某人开始另一个警报吗?有时那会很好。我认为答案是试图说“正确看待事物”。完美的分布式解决方案有其自身的缺点,有时越简单越好。
6赞 Javier 4/20/2010 #4

有一些策略;但是我所知道的没有一个可以真正分布并给出真正的序列。

  1. 有一个中央数字生成器。它不一定是一个大数据库。 有一个快速的原子计数器,在绝大多数情况下,它对你的整个集群来说已经足够快了。memcached
  2. 为每个节点分隔一个整数范围(如 Steven Schlanskter 的答案)
  3. 使用随机数或 UUID
  4. 使用一些数据,以及节点的 ID,并对其进行所有哈希处理(或对其进行 hmac 处理)

就我个人而言,如果我想拥有一个几乎连续的空间,我会倾向于 UUID 或 memcached。

18赞 Paolo 10/5/2010 #5

现在有更多选择。

虽然这个问题很“老”,但我到了这里,所以我认为留下我所知道的选项(到目前为止)可能会有所帮助:

  • 你可以试试Hazelcast。在 1.9 版本中,它包括 java.util.concurrent.AtomicLong 的分布式实现
  • 您也可以使用 Zookeeper。它提供了创建序列节点的方法(附加到 znode 名称,尽管我更喜欢使用节点的版本号)。不过要小心这一点:如果你不希望在序列中遗漏数字,它可能不是你想要的。

干杯

评论

3赞 Jonathan Holloway 10/5/2010
Zookeeper 是我选择的选项,在我开始的邮件列表中有一个很好的描述和写法 - mail-archive.com/[email protected]/msg01967.html
0赞 Paolo 10/6/2010
乔恩,感谢您指出该线程,这正是我正在考虑的解决方案类型。顺便说一句,你制作的代码是为了克服MAX_INT限制吗?
139赞 user84609 4/16/2011 #6

好吧,这是一个非常古老的问题,我现在第一次看到。

您需要区分序列号唯一 ID,这些 ID (可选)可按特定条件(通常是生成时间)松散排序。真正的序列号意味着知道所有其他工作线程都做了什么,因此需要共享状态。没有简单的方法可以以分布式、大规模的方式做到这一点。您可以研究诸如网络广播、每个工作线程的窗口范围以及唯一工作线程 ID 的分布式哈希表之类的内容,但这需要做很多工作。

唯一 ID 是另一回事,有几种以去中心化方式生成唯一 ID 的好方法:

a) 您可以使用 Twitter 的 Snowflake ID 网络服务Snowflake是一个:

  • 网络服务,即您进行网络调用以获取唯一 ID;
  • 生成按生成时间排序的 64 位唯一 ID;
  • 并且该服务具有高度可扩展性和(潜在)高可用性;每个实例每秒可以生成数千个 ID,您可以在 LAN/WAN 上运行多个实例;
  • 用 Scala 编写,在 JVM 上运行。

b) 您可以使用UUID 和 Snowflake 的 ID 的制作方式派生的方法,在客户端本身上生成唯一 ID。有多种选择,但大致如下:

  • 最高有效位 40 位左右:时间戳;ID 的生成时间(我们使用最高有效位作为时间戳,以使 ID 可按生成时间排序。

  • 接下来的 14 位左右:每个生成器计数器,每个生成器每生成一个新 ID 递增 1。这可确保在同一时刻生成的 ID(相同的时间戳)不会重叠。

  • 最后 10 位左右:每个生成器的唯一值。使用它,我们不需要在生成器之间进行任何同步(这非常困难),因为所有生成器都会因为这个值而生成不重叠的 ID。

c) 您可以在客户端上生成 ID,只需使用时间戳和随机值。这样就无需了解所有生成器,并为每个生成器分配一个唯一的值。另一方面,此类 ID 不能保证是全局唯一的,它们只是极有可能是唯一的。(要进行碰撞,一个或多个生成器必须在同一时间创建相同的随机值。大致如下:

  • 最重要的 32 位:时间戳,ID 的生成时间。
  • 最低有效 32 位:32 位随机性,为每个 ID 重新生成。

d) 简单的出路,使用 UUID / GUID

评论

0赞 Piyush Kansal 1/17/2015
Cassandra 支持计数器 (cassandra.apache.org/doc/cql3/CQL.html#counters),但有一些限制。
0赞 brucenan 11/13/2015
序列号很容易为位图索引设置位置,但唯一 ID 有时太长(64 位或 128 位),唯一 ID 如何映射到位图索引位置?谢谢。
2赞 puneet 7/22/2016
真的很喜欢选项 #b .....它可以允许大规模,并且不会引起太多的并发问题
3赞 Navin 8/9/2018
twitter/snowflake不再维护
1赞 tonix 10/12/2020
Stack Overflow 如何为其用户生成序列号和唯一 ID?似乎他们的用户 ID 确实是连续/顺序和唯一的。你认为他们有一项服务被所有客户击中吗?但是,如果大量新用户在同一时间点注册,这不会导致瓶颈吗?谢谢!
12赞 Nikita Koksharov 1/12/2014 #7

这可以通过 Redisson 来完成。它实现了 的分布式和可扩展版本。下面是示例:AtomicLong

Config config = new Config();
config.addAddress("some.server.com:8291");

Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();
0赞 Majid Azimi 7/14/2014 #8

我编写了一个简单的服务,可以生成半唯一的非顺序 64 位长数字。它可以部署在多台机器上,以实现冗余和可扩展性。它使用 ZeroMQ 进行消息传递。有关其工作原理的更多信息,请查看 github 页面:zUID

0赞 user2108278 11/25/2016 #9

使用数据库,单个内核可以达到每秒 1.000+ 的增量。这很容易。您可以使用它自己的数据库作为后端来生成该数字(因为在 DDD 术语中,它应该是它自己的聚合)。

我似乎遇到了类似的问题。我有几个分区,我想为每个分区获得一个偏移计数器。我实现了这样的东西:

CREATE DATABASE example;
USE example;
CREATE TABLE offsets (partition INTEGER, offset LONG, PRIMARY KEY (partition));
INSERT offsets VALUES (1,0);

然后执行以下语句:

SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+1 WHERE partition=1;

如果你的应用程序允许你,你可以一次分配一个块(这就是我的情况)。

SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+100 WHERE partition=1;

如果你需要进一步的吞吐量,而不能提前分配偏移量,你可以使用 Flink 实现自己的服务进行实时处理。我能够获得每个分区大约 100K 的增量。

希望对你有所帮助!

评论

0赞 Nulik 2/11/2021
数据库不是一个分布式系统,而是一个集中式系统
2赞 SANN3 4/5/2017 #10

分布式 ID 生成可以使用 Redis 和 Lua 进行存档。Github 中提供的实现。它生成一个分布式的、可 k 排序的唯一 ID。

0赞 user1860223 8/23/2017 #11

问题类似于: 在 iscsi 世界中,每个 lun/卷都必须由客户端上运行的启动器唯一标识。 iscsi 标准规定,前几位必须表示存储提供商/制造商信息,其余部分单调递增。

同样,可以使用分布式节点系统中的初始位来表示 nodeID,其余位可以单调递增。

评论

1赞 Onic Team 8/23/2017
请补充一些细节
0赞 refuess 3/26/2018 #12

一个不错的解决方案是使用基于长时间的生成。 它可以在分布式数据库的支持下完成。

2赞 Zohair 3/28/2018 #13

我知道这是一个老问题,但我们也面临着同样的需求,无法找到满足我们需求的解决方案。 我们的要求是获得一个唯一的 id 序列 (0,1,2,3...n),因此 snowflake 没有帮助。 我们创建了自己的系统来使用 Redis 生成 id。Redis 是单线程的,因此它的列表/队列机制总是一次给我们 1 个弹出。

我们所做的是,我们创建一个 id 缓冲区,最初,队列将有 0 到 20 个 id,这些 id 可以在请求时调度。多个客户端可以请求一个 id,redis 一次会弹出 1 个 id,每次从左边弹出后,我们在右侧插入 BUFFER + currentId,这样缓冲区列表就会继续运行。在这里实现

评论

1赞 tonix 10/12/2020
您的 Redis 解决方案是否可很好地扩展?如果是,每秒有多少个并发请求?谢谢!
1赞 Zohair 10/13/2020
嘿 Tonix,我们确实使用了几个月,但它没有经过大规模测试。我建议你探索 Redis INCR
0赞 tonix 10/13/2020
你现在用什么?
1赞 Zohair 10/13/2020
我们的问题陈述已经过时了 - 但如果我必须再次解决这个问题,我肯定会使用 Redis INCR。
0赞 ZAky 1/14/2022 #14

我花两分钱买 gcloud。使用存储文件。

作为云函数实现,可以很容易地转换为库。

https://github.com/zaky/sequential-counter