建议的解决方案:在分布式环境中生成唯一ID

时间:2013-08-15 08:10:30

标签: php distributed couchbase

我一直在浏览网络,试图找到一种解决方案,使我们能够在区域分布式环境中生成唯一ID。

我查看了以下选项(其中包括):

SNOWFLAKE(通过Twitter)

  • 这似乎是一个很好的解决方案,但我不喜欢为了创建ID而必须管理另一个软件的额外复杂性;
  • 现阶段缺乏文档,因此我认为这不是一项好的投资;
  • 节点需要能够使用Zookeeper相互通信(延迟/通信失败怎么办?)

UUID

  • 请看一下: 550e8400-e29b-41d4-a716-446655440000 ;
  • 它是一个128位ID;
  • 有一些已知的碰撞(取决于我猜的版本)see this post

像MYSQL一样的关系数据库中的自动识别

  • 这似乎是安全的,但不幸的是,我们没有使用关系数据库(可伸缩性首选项);
  • 我们可以为此部署一个MySQL服务器,就像Flickr所做的那样,但同样,这又引入了另一个故障点/瓶颈。还增加了复杂性。

非关系数据库中的自动识别类似

  • 这可能有效,因为我们使用Couchbase作为我们的数据库服务器,但是;
  • 当我们在不同地区有多个群集,延迟问题,网络故障时,这将不起作用:在某些时候,ID会根据流量的数量发生冲突;

我建议的解决方案(这是我需要帮助的)

假设我们在5个不同地区(非洲,欧洲,亚洲,美洲和大洋洲)拥有由10个Couchbase节点和10个应用节点组成的集群。这是为了确保从最靠近用户的位置提供内容(以提高速度)并确保在发生灾难时的冗余等。

现在,任务是生成在复制(和平衡)发生时不会发生冲突的ID,我认为这可以通过3个步骤实现:

第1步

将为所有区域分配整数ID(唯一标识符):

  • 1 - 非洲;
  • 2 - 美国;
  • 3 - 亚洲;
  • 4 - 欧洲;
  • 5 - Ociania。

第2步

为添加到群集的每个应用程序节点分配一个ID,记住一个群集中最多可能有99 999个服务器(尽管我怀疑:这是一个安全的预防措施)。这看起来像这样(假IP):

  • 00001 - 192.187.22.14
  • 00002 - 164.254.58.22
  • 00003 - 142.77.22.45
  • 等等。

请注意,所有这些都在同一个群集中,这意味着您可以在每个区域拥有节点00001。

第3步

对于插入数据库的每条记录,将使用递增的ID来识别它,这就是它的工作方式:

Couchbase提供了一种增量功能,可用于在群集内部创建ID。为确保冗余,将在群集中创建3个副本。由于它们位于同一个地方,我认为应该可以安全地假设,除非整个群集关闭,否则其中一个节点将可用,否则可以增加一些副本。

将所有内容整合在一起

假设用户正在从欧洲注册: 提供请求的应用程序节点将获取区域代码(在这种情况下 4 ),获取自己的ID(例如 00005 ),然后获取递增的ID( 1 )来自Couchbase(来自同一群集)。

我们最终得到3个组件:4, 00005,1。现在,要从中创建ID,我们可以将这些组件加入4.00005.1。为了使它更好(我对此不太确定),我们可以连接(而不是添加它们)组件,最终得到:4000051

在代码中,这看起来像这样:

$id = '4'.'00005'.'1';

注意:不是$id = 4+00005+1;

赞成

  • ID看起来比UUID好;
  • 他们看起来很独特。即使另一个区域中的节点生成了相同的递增ID并且具有与上述节点相同的节点ID,我们也始终使用区域代码将它们分开;
  • 它们仍然可以存储为整数(可能是Big Unsigned整数);
  • 它是架构的一部分,没有额外的复杂性。

缺点

  • 没有排序(或有没有)?
  • 这是我需要您输入的内容(大多数)

我知道每个解决方案都有缺陷,可能还有我们在表面上看到的更多。你能发现这整个方法的任何问题吗?

提前感谢您的帮助: - )

修改

正如@DaveRandom建议的那样,我们可以添加第4步:

第4步

我们可以生成一个随机数并将其附加到ID以防止可预测性。实际上,你最终会得到这样的结论:

4000051357而非4000051

2 个答案:

答案 0 :(得分:1)

您担心ID有两个原因:

  1. 复杂网络基础架构中可能发生冲突
  2. 外观
  3. 从第二个问题开始,外观。虽然UUID在标识符方面肯定不是一个很好的美,但是当您在复杂的数据中心(或数据中心)中引入真正唯一的数字时,回报会逐渐减少。我不相信,当在Web应用程序的URL中使用长数字而不是UUID时,应用程序的感知会发生巨大变化。理想情况下,两者都不会被显示,ID只会通过Ajax请求等发送。虽然一个漂亮,干净的令人难忘的URL是可取的,但它从来没有阻止我在亚马逊购物(他们有绝对可怕的URL) 。 :)

    即使你的提议,标识符虽然它们的字符数比UUID短,但它们不比UUID更令人难忘。因此,外观可能仍有争议。

    谈到第一点......,是的,有一些情况下UUID已知会产生冲突。虽然这不应该在一个配置合理且一致的架构中发生,但我可以看到它会如何发生(但我个人对此并不那么担心)。

    所以,如果你在讨论替代方案,我会成为MongoDB ObjectId的简单性及其生成ID时避免重复的技术的粉丝。完整文档为here。快速相关部分在几个方面与您的潜在设计类似:

    ObjectId是一个12字节的BSON类型,使用:

    构造
    • 一个4字节的值,表示自Unix纪元以来的秒数,
    • 一个3字节的机器标识符,
    • 一个2字节的进程ID,
    • 一个3字节的计数器,以随机值开始。

    时间戳通常可用于排序。计算机标识符与具有唯一ID的应用程序服务器类似。进程id只是附加的熵,最后为了防止冲突,只要时间戳与上次生成ObjectId的时间戳相同,就会有一个自动递增的计数器(这样可以快速创建ObjectIds)。 ObjectId可以在客户端或数据库上生成。此外,ObjectIds占用的字节数比UUID少(但只有4个)。当然,您无法使用时间戳并丢弃4个字节。

    为了澄清,我不是建议您使用MongoDB,而是受其用于ID生成的技术的启发。

    所以,我认为您的解决方案很不错(也许您希望受到MongoDB的独特ID实现的启发)并且可行。至于你是否需要这样做,我认为这是一个只有你能回答的问题。

答案 1 :(得分:1)

我认为这看起来很稳固。每个区域都保持一致性,如果使用XDCR,则不会发生冲突。 INCR在集群中是原子的,因此您不会遇到任何问题。您实际上不需要将机器代码作为其中的一部分。如果某个区域内的所有应用服务器都连接到同一个群集,则将其中的00001部分中断是无关紧要的。如果由于其他原因(某种分析)对您有用,那么无论如何都是如此,但没有必要。

所以它可以简单地为'4'。 1'(使用你的例子)

你能举个例子说明你需要什么样的“排序”吗?

首先:添加熵的一个缺点(我不确定你为什么需要它),你不能轻易地迭代ID集合。

例如:如果您的ID是1-100,您将从计数器键上的简单GET查询中获知,您可以按组分配任务,此任务需要1-10,接下来的11-20等等,工人可以并行执行。如果添加熵,则需要使用Map / Reduce View来提取集合,这样就会失去键值模式的好处。

第二:由于您关注可读性,因此添加文档/对象类型标识符也很有价值,这可以在Map / Reduce Views中使用(或者您可以使用json关键来识别那个)。

前:'你:'。 '4'。 '1'

如果您在外部引用ID,您可能希望以其他方式隐藏。如果你需要一个例子,请告诉我,我可以用你能做的事情来补充我的答案。

@scalabl3