120

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

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

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

4

14 回答 14

134

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

您需要区分序列号唯一 ID,它们(可选)可按特定标准(通常是生成时间)松散排序。真正的序列号意味着知道所有其他工作人员所做的事情,因此需要共享状态。以分布式、大规模的方式做到这一点并不容易。您可以查看网络广播、每个工作人员的窗口范围以及唯一工作人员 ID 的分布式哈希表等内容,但这需要大量工作。

唯一 ID 是另一回事,有几种以分散方式生成唯一 ID 的好方法:

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

  • 网络化服务,即您通过网络调用获取唯一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) 简单的出路,使用 UUIDs / GUIDs

于 2011-04-16T10:21:29.670 回答
18

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

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

独特的 :-)

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

于 2010-04-20T01:08:34.167 回答
17

现在有更多选择。

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

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

干杯

于 2010-10-04T19:13:45.543 回答
12

它可以用Redisson完成。它实现了分布式和可扩展版本的AtomicLong. 这是示例:

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

Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();
于 2014-01-12T06:56:01.803 回答
8

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

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

于 2010-04-20T01:22:35.393 回答
6

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

  1. 有一个中央号码发生器。它不必是一个大数据库。 memcached有一个快速的原子计数器,在绝大多数情况下它对你的整个集群来说已经足够快了。
  2. 为每个节点分隔一个整数范围(如Steven Schlanskter 的回答
  3. 使用随机数或 UUID
  4. 使用一些数据,连同节点的 ID,并将其全部散列(或hmac

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

于 2010-04-20T01:28:07.387 回答
5

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

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

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

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

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

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

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

于 2010-04-20T01:20:59.533 回答
2

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

于 2017-04-05T09:50:38.610 回答
2

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

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

于 2018-03-28T05:36:29.597 回答
0

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

于 2014-07-14T07:05:33.303 回答
0

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

于 2018-03-26T04:24:58.173 回答
0

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

实现为云功能,可以轻松转换为库。

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

于 2022-01-13T19:50:48.313 回答
0

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

类似地,可以使用分布式节点系统中的初始位来表示节点ID,其余的可以单调递增。

于 2017-08-23T03:54:52.860 回答
0

使用数据库,单核每秒可以达到 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 的增量。

希望能帮助到你!

于 2016-11-25T09:18:20.903 回答