分布式序列号生成?


103

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

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

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


这个问题是老了,但请看到我的新的答案stackoverflow.com/questions/2671858/...
加斯帕中号

您如何使用nextval.org?该网站有点奇怪,我不知道这是怎么回事。这是一些Unix命令吗?还是一些云服务?
diegosasw

Answers:


116

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

您需要区分序列号唯一ID,这些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)简单的方法是使用UUID / GUID


Cassandra支持计数器(cassandra.apache.org/doc/cql3/CQL.html#counters),尽管有一些限制。
Piyush Kansal,2015年

序列号很容易设置位图索引的位置,但是唯一ID有时太长(64位或128位),如何将唯一ID映射到位图索引位置?谢谢。
brucenan

2
非常喜欢选项#b .....它可以允许大规模使用并且不会引起太多的并发问题
puneet '16

2
twitter/snowflake不再维护
Navin

如果您想获得选项B的Apache2许可实现,请查看bitbucket.org/pythagorasio/common-libraries/src/master/…。您也可以从maven io.pythagoras.common:distributed-sequence-id-generator:1.0获得它。 .0
Wpigott

16

现在有更多选择。

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

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

干杯


3
我选择了Zookeeper,在我开始的邮件列表中有一个很好的描述和说明-mail-archive.com/zookeeper-user@hadoop.apache.org/msg01967.html
乔恩

乔恩,感谢您指向该线程,这正是我在考虑的解决方案类型。顺便说一句,您是否编写了克服MAX_INT限制的代码?
保罗

15

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

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

独特 :-)

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


1
我喜欢您关于批次ID生成的观点,但这仅限制了任何实时计算的可能性。
ishan

我已经实现了类似的机制。这样,除了客户端缓存序列块之外,我还添加了一些缓存序列块的服务器主机。一个(单个)主生成器维护在某些高可用性的存储中或一个单主主机中,只能访问服务器主机群。服务器缓存还可以帮助我们延长正常运行时间,尽管单个主服务器会出现故障。
Janakiram 2015年

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

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

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


3
...当运行该服务的服务器出现故障时会发生什么?
纳文

是否有警报通知某人开始另一个?有时候那会很好。我认为答案是试图说“保持透视”。完美的分布式解决方案有其自身的缺点,有时越简单越好。
nic ferrier

6

有一些策略;但我所知道的任何一个都无法真正分发并给出真实的顺序。

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

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


5

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

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

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

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

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

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

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


1
当UUID起作用时,它们的问题在于,如果最终需要索引生成的密钥,则必须小心如何存储它们。它们通常还比单调增加的序列占用更多的空间。有关使用MySQL存储它们的讨论,请参见percona.com/blog/2014/12/19/store-uuid-optimized-way
Pavel

2

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


2

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

我们要做的是,我们创建一个id缓冲区,最初,该队列将具有0到20个ID,这些ID可在被请求时立即分派。多个客户端可以请求一个id,redis一次将弹出1个id,每次从左侧弹出之后,我们都会在右侧插入BUFFER + currentId,以使缓冲区列表继续运行。在这里实施


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世界中,每个lun /音量必须由在客户端运行的启动器唯一地标识。iscsi标准说,前几位必须代表存储提供者/制造商的信息,其余的则单调递增。

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


1
请添加更多详细信息
Ved Prakash

0

一种不错的解决方案是使用基于长时间的发电。可以通过分布式数据库的支持来完成。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.