分布式序列号生成?

问题描述 投票:91回答:13

我一般在过去使用数据库序列实现序列号生成。

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

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

java apache-zookeeper sequences
13个回答
110
投票

好的,这是一个非常古老的问题,我现在才开始看到。

您需要区分序列号和唯一ID(可选)按特定条件(通常为生成时间)进行松散排序。真正的序列号意味着知道所有其他工作者所做的事情,因此需要共享状态。没有简单的方法以分布式,高规模的方式这样做。您可以查看网络广播,每个工作人员的窗口范围和distributed hash tables for unique worker IDs等内容,但这需要做很多工作。

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

a)你可以使用Twitter's Snowflake ID network service。雪花是:

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

b)您可以使用从how UUIDs派生的方法生成客户端本身的唯一ID,并制作Snowflake的ID。有多种选择,但有以下几点:

  • 最重要的40位左右:时间戳; ID的生成时间。 (我们使用时间戳的最高位来按生成时间对ID进行排序。)
  • 接下来的14位左右:每个发生器计数器,每个生成器为每个生成的新ID递增1。这可确保在同一时刻生成的ID(相同时间戳)不重叠。
  • 最后10位:每个发生器的唯一值。使用这个,我们不需要在生成器之间进行任何同步(这是非常困难的),因为所有生成器都会因为此值而生成非重叠的ID。

c)您可以仅使用时间戳和随机值在客户端上生成ID。这避免了需要知道所有生成器,并为每个生成器分配唯一值。另一方面,这些ID不能保证全局唯一,它们很可能是唯一的。 (要碰撞,一个或多个生成器必须在同一时间创建相同的随机值。)以下内容:

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

d)简单的出路,use UUIDs / GUIDs


0
投票

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


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的增量。

希望能帮助到你!


0
投票

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

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


0
投票

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


15
投票

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

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

独特 :-)

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


14
投票

现在有更多的选择。

你这个问题是“老”,我来到这里,所以我认为留下我所知道的选项(到目前为止)可能是有用的:

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

干杯


11
投票

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

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

Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();

8
投票

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

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


6
投票

有一些策略;但我知道的任何一个都不能真正分布并给出真实的序列。

  1. 有一个中央数字生成器。它不一定是一个大数据库。 memcached有一个快速的原子计数器,在绝大多数情况下它对你的整个集群来说足够快。
  2. 为每个节点分隔一个整数范围(如Steven Schlanskter's answer
  3. 使用随机数或UUID
  4. 使用一些数据和节点的ID,并将其全部哈希(或hmac

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


4
投票

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

我应该扩展这一点。

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

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

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

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

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


2
投票

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

我们所做的是,我们创建一个id的缓冲区。最初,队列将有0到20个ID,可以在请求时分派。多个客户端可以请求一个id,redis将一次弹出1个id。在左边的每次弹出之后,我们向右边插入BUFFER + currentId,这样可以保持缓冲区列表的运行。实施here


1
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.