【问题标题】:Encoding a number with a suffix or prefix in a compact way以紧凑的方式对带有后缀或前缀的数字进行编码
【发布时间】:2018-09-18 17:27:04
【问题描述】:

假设我有 6 位数的订单 ID:

000000
000001
000003
...
000020
...
999999

并假设这些中的每一个都来自分布式系统中的不同节点,我想将节点 ID 编码到订单中。 最简单的方法是简单地保留节点 ID 的前 2 位,如下所示:

010000 - second node
020001 - third node
010003 - second node again
150004 - 16th node
...

这工作得很好,但是因为我确定我只期望少量节点(比如说 16 个),所以我失去了很多可能的 id,将自己限制在基本上 10^4 而不是 10^6 .有没有一种聪明的方法来编码 15 个唯一节点而不限制可能的数量?理想情况下,我会有10^6 - 15 的可能性。

编辑:我正在寻找一种不会将范围平均分配给每个节点 ID 的解决方案。我正在寻找一种将节点 id 编码为已经存在的唯一 id 的方法,而不会丢失(理想情况下)更多的可能性节点数。

EDIT2:这必须是 6 位数字的字符串表示的原因是因为我正在使用的 API 需要这个。不幸的是,没有办法解决它。

【问题讨论】:

  • 如果你考虑的是位而不是数字,你可以用 4 位编码 15 个节点。
  • 您是否将这些数字存储或显示为 6 位十进制数字?因为存储高达 999999 的整数所需的 32 位整数有 12 个未使用的位可用于存储其他信息。
  • @m69 尽管听起来很愚蠢,但一些 API 设计人员认为 6 位数字已经足够了,所以我实际上必须发送 6 个 digit 数字的代表作为一个字符串。所以不,我不能使用任何剩余的位,不幸的是:(
  • @juvian 我试过了。问题是,6 位数字的最接近表示是 2^20。所以,20位。但是,它在 7 位数区域中略高。我现在正在想办法限制它。
  • 感谢您在 cmets 中进行澄清(我会在问题中提及 API 设计)——这让挑战变得更有趣!

标签: algorithm language-agnostic uniqueidentifier


【解决方案1】:

我失去了很多可能的 id,将自己限制在基本上 10^4 而不是 10^6。

我们仍然有10^4 * 16 ids总共

有没有一种聪明的方法来编码 15 个唯一节点而不限制可能的数量?

这个问题类似于distributed hash table 键空间分区。该问题最著名的解决方案是创建大量虚拟节点,在这些虚拟节点之间划分密钥空间,然后以特定方式(循环、随机、按需等)将这些虚拟节点分配给物理节点。

实现 keyspace 分区最简单的方法是确保每个节点生成这样一个 id,即:

vnode_id = order_id % total_number_of_vnodes

例如,如果我们只有 3 个 vnode [0, 1, 2] 那么:

vnode 0 must generate ids: 0, 3, 6, 9...
vnode 1 must generate ids: 1, 4, 7, 10...
vnode 2 must generate ids: 2, 5, 7, 11...

如果我们有 7 个 vnode [0, 1, 2, 3, 4, 5, 6] 那么:

vnode 0 must generate ids: 0, 7, 14, 21... 
vnode 1 must generate ids: 1, 8, 15, 22... 
vnode 2 must generate ids: 2, 9, 16, 23... 
...
vnode 6 must generate ids: 6, 13, 20, 27... 

那么所有物理节点都必须以已知和常用的方式映射到虚拟,例如1:1映射:

physical node 0 takes vnode 0
physical node 1 takes vnode 1
physical node 2 takes vnode 2

按需映射:

physical node 0 takes vnode 0, 3, 7 (many orders)
physical node 1 takes vnode 1, 4 (less orders)
physical node 2 takes vnode 2 (no orders) 

我希望你能理解这个想法。

理想情况下,我会有 10^6 - 15 种可能性。

很遗憾,这是不可能的。考虑一下:我们有 10^6 个可能的 id 和 15 个不同的节点,每个节点生成一个 unique id。

基本上,这意味着我们以一种或另一种方式在节点之间划分我们的 id,即每个节点平均得到10^6 / 15,这远低于理想的10^6 - 15

使用上面描述的方法,我们仍然有10^6 ids,但是它们将在vnodes之间进行分区,这些vnodes又将映射到物理节点。这是解决您的问题 AFAIK 的最佳实用解决方案。

我正在寻找一种不会将范围平均分配给每个节点 ID 的解决方案。我正在寻找一种将节点 id 编码为已经存在的唯一 id 的方法,而不会丢失(理想情况下)更多的可能性节点数。

不要期待奇迹。可能还有很多其他值得尝试的技巧。

例如,如果 Server 和所有 Client 都知道下一个订单 id 必须是 235,但假设 Client 5 生成订单 id 240 (235 + 5) 并将其发送给 Server。

服务器需要订单 id 235,但收到订单 id 240。所以现在服务器知道这个订单来自客户端 5 (240 - 235)。

或者我们可以尝试使用另一个字段来存储客户端 ID。例如,如果您有一个时间字段 (HH:MM.SS),我们可能会使用秒来存储客户端 ID。

只是一些例子,我想你明白了......

【讨论】:

    【解决方案2】:

    n 为 6 位输入的前 2 位所代表的数字。假设您有 16 个节点,我们可以这样做:

    nodeId = n % 16 
    

    还有:

    highDigit = n / 16
    

    其中/ 表示整数除法。对于 16 个节点,highDigit = [0..6]

    如果m是输入的最后4位代表的数字,那么我们可以通过以下方式恢复原始订单id:

    orderId = highDigit*10^5 + m
    

    采用这种方案,16个节点,可以表示6*10^5 + 10^4个订单id。

    【讨论】:

      【解决方案3】:

      您可以将 10^6 个可能的 ID 拆分为接近相等的块,其中每个块的起始索引等于 10^6 除以块数,向下舍入,乘以块索引,然后块大小为 10^6 除以块数,向下取整。在您的示例中,有 16 个块:

      10^6 / 16 = 62,500
      chunk1:  [     0,   62500)
      chunk2:  [ 62500,  125000)
      chunk3:  [125000,  187500)
      chunk4:  [187500,  250000)
      chunk5:  [250000,  312500)
      chunk6:  [312500,  375000)
      chunk7:  [375000,  437500)
      chunk8:  [437500,  500000)
      chunk9:  [500000,  562500)
      chunk10: [562500,  625000)
      chunk11: [625000,  687500)
      chunk12: [687500,  750000)
      chunk13: [750000,  812500)
      chunk14: [812500,  875000)
      chunk15: [875000,  937500)
      chunk16: [937500, 1000000)
      

      要从节点 X 上的本地 ID 计算全局 ID,请计算 62500 * X + 本地 ID。要从节点中确定节点和本地 ID,请计算节点 = 全局 ID / 62500 向下舍入,本地 ID = 全局 ID mod 62500。

      这样做,您基本上可以使用所有可用索引,直至舍入误差。与节点之间的 I/O 相比,整数的除法和模数应该相对较快。

      【讨论】:

        【解决方案4】:

        由于您已选择使用数字(而不是位,我们可以将整个练习压缩为 32 位数字),因此这里有一种编码节点 ID 的方法。也许其他人可以提出更多的想法。

        将数字字母表扩展到J。想象一下节点 ID 的位分布在六位数字上。对于每个设置位,将订单 ID 的十进制数字映射为一个字母:

        0 -> A
        1 -> B
        2 -> C
        ...
        9 -> J
        

        例如:

        {759243, 5} -> 759C4D
        

        现在您可以将所有 10^6 个订单 ID 与 6 位节点 ID 一起编码。

        【讨论】:

        • 那行得通。以类似于编码基数 16 的方式。但不幸的是,最终结果应该是一个 6 digit 字符串(没有字母)。但是,我们也可以使用位,只要结果是 6 位唯一字符串(例如,使用前 4 位甚至 8 位是一个想法,但由于 6 位数字的最接近的代表是2^20,但有些值(略高于 10^7)是 7 位数字(我设法只为 7 位数字做了一个工作算法)
        • @ValentinRadu “数字”是如何表示的?为什么要限制我们选择哪些角色? (每个十进制数字通常是用一个字节表示的 ASCII 字符库的一部分)
        • @ValentinRadu 我(和其他人)得到的是一个 32 位数字,可以存储所有相关信息,并且仍然允许您在任何时候表示 6 digit 字符串像这样“显示”它。
        • 这个约束是我无法控制的。设计 API 的人决定:仅 6 位字符串:(例如 000338、999223 等)。添加字符将从 API 返回错误。我知道这是一个愚蠢的约束,但它就在那里。
        猜你喜欢
        • 2018-11-13
        • 2010-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-05
        相关资源
        最近更新 更多