提问人:Jonathan Holloway 提问时间:4/20/2010 最后编辑:Lorenzo BelliJonathan Holloway 更新时间:1/14/2022 访问量:92268
分布式序列号生成?
Distributed sequence number generation?
问:
过去,我通常使用数据库序列实现序列号生成。
例如,使用 Postgres SERIAL 类型 http://www.neilconway.org/docs/sequences/
我很好奇如何为没有数据库的大型分布式系统生成序列号。有没有人有任何经验或建议,可以以线程安全的方式为多个客户端实现序列号生成?
答:
您可以让每个节点都有一个唯一的 ID(无论如何您都可能拥有该 ID),然后将其附加到序列号之前。
例如,节点 1 生成序列 001-00001 001-00002 001-00003 等,节点 5 生成序列 005-00001 005-00002
独一无二:-)
或者,如果你想要某种集中式系统,你可以考虑让你的序列服务器分块发布。这大大减少了开销。例如,您不是从中央服务器请求必须为每个必须分配的 ID 请求新 ID,而是从中央服务器请求 10,000 个块的 ID,然后只需在用完时执行另一个网络请求。
评论
为什么不使用(线程安全的)UUID 生成器?
我可能应该对此进行扩展。
UUID 保证是全局唯一的(如果您避免使用基于随机数的 UUID,其中唯一性是极有可能的)。
无论您使用多少个 UUID 生成器,每个 UUID 的全局唯一性都会满足您的“分布式”要求。
通过选择“线程安全”UUID 生成器,可以满足您的“线程安全”要求。
假定每个 UUID 的全局唯一性保证满足“序列号”要求。
请注意,许多数据库序列号实现(例如 Oracle)不能保证单调递增或(甚至)递增序列号(基于每个“连接”)。这是因为在每个连接的基础上,在“缓存”块中分配了一批连续的序列号。这保证了全球唯一性并保持了足够的速度。但是,当多个连接分配时,实际分配的序列号(随着时间的推移)可能会混乱!
评论
如果它真的必须是全局顺序的,而不仅仅是唯一的,那么我会考虑创建一个单一的、简单的服务来分配这些数字。
分布式系统依赖于许多小服务的交互,对于这种简单的任务,你真的需要还是真的会从其他复杂的分布式解决方案中受益?
评论
有一些策略;但是我所知道的没有一个可以真正分布并给出真正的序列。
- 有一个中央数字生成器。它不一定是一个大数据库。 有一个快速的原子计数器,在绝大多数情况下,它对你的整个集群来说已经足够快了。
memcached
- 为每个节点分隔一个整数范围(如 Steven Schlanskter 的答案)
- 使用随机数或 UUID
- 使用一些数据,以及节点的 ID,并对其进行所有哈希处理(或对其进行 hmac 处理)
就我个人而言,如果我想拥有一个几乎连续的空间,我会倾向于 UUID 或 memcached。
现在有更多选择。
虽然这个问题很“老”,但我到了这里,所以我认为留下我所知道的选项(到目前为止)可能会有所帮助:
- 你可以试试Hazelcast。在 1.9 版本中,它包括 java.util.concurrent.AtomicLong 的分布式实现
- 您也可以使用 Zookeeper。它提供了创建序列节点的方法(附加到 znode 名称,尽管我更喜欢使用节点的版本号)。不过要小心这一点:如果你不希望在序列中遗漏数字,它可能不是你想要的。
干杯
评论
好吧,这是一个非常古老的问题,我现在第一次看到。
您需要区分序列号和唯一 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。
评论
twitter/snowflake
不再维护
这可以通过 Redisson 来完成。它实现了 的分布式和可扩展版本。下面是示例:AtomicLong
Config config = new Config();
config.addAddress("some.server.com:8291");
Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();
我编写了一个简单的服务,可以生成半唯一的非顺序 64 位长数字。它可以部署在多台机器上,以实现冗余和可扩展性。它使用 ZeroMQ 进行消息传递。有关其工作原理的更多信息,请查看 github 页面:zUID
使用数据库,单个内核可以达到每秒 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 的增量。
希望对你有所帮助!
评论
分布式 ID 生成可以使用 Redis 和 Lua 进行存档。Github 中提供的实现。它生成一个分布式的、可 k 排序的唯一 ID。
问题类似于: 在 iscsi 世界中,每个 lun/卷都必须由客户端上运行的启动器唯一标识。 iscsi 标准规定,前几位必须表示存储提供商/制造商信息,其余部分单调递增。
同样,可以使用分布式节点系统中的初始位来表示 nodeID,其余位可以单调递增。
评论
一个不错的解决方案是使用基于长时间的生成。 它可以在分布式数据库的支持下完成。
我知道这是一个老问题,但我们也面临着同样的需求,无法找到满足我们需求的解决方案。 我们的要求是获得一个唯一的 id 序列 (0,1,2,3...n),因此 snowflake 没有帮助。 我们创建了自己的系统来使用 Redis 生成 id。Redis 是单线程的,因此它的列表/队列机制总是一次给我们 1 个弹出。
我们所做的是,我们创建一个 id 缓冲区,最初,队列将有 0 到 20 个 id,这些 id 可以在请求时调度。多个客户端可以请求一个 id,redis 一次会弹出 1 个 id,每次从左边弹出后,我们在右侧插入 BUFFER + currentId,这样缓冲区列表就会继续运行。在这里实现
评论
我花两分钱买 gcloud。使用存储文件。
作为云函数实现,可以很容易地转换为库。
评论