为什么要将分片 ID 附加到生成的 ID?

Why would you append a shard ID to a generated ID?

我正在读这个: https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c

在最后一节 "Solution" 中,他们根据数据库的自动增量功能 + 自纪元以来的毫秒数 + 分片 ID 生成全局唯一 ID。

为什么我们需要附加分片 ID?

具体来说,它说

Next, we take the shard ID for this particular piece of data we’re trying to insert. Let’s say we’re sharding by user ID, and there are 2000 logical shards; if our user ID is 31341, then the shard ID is 31341 % 2000 -> 1341. We fill the next 13 bits with this value

这没有意义:如果您已经mod按分片数量 (31341 % 2000) 指定用户 ID,则意味着 1) 您已经拥有用户 ID! 2) 你已经通过mod函数知道它属于哪个分片了!

我哪里误会了?

他们需要一个 64 位长度的图片 ID。

自纪元以来的毫秒数 41 位 + 分片 ID 的 13 位 + 自动增量值的 10 位。

他们使用 shard-id 而不是 user-id 只是因为只有 shard-id 适合 13 位,而 user-id 需要更多位。

也许我可以为您更好地分解它,而且它不是只是,因为用户 ID 不适合。

他们正在使用 Twitter Snowflake ID。这旨在跨多个服务器、跨多个数据中心并行生成唯一 ID。例如,在同一时刻,两个 "items" 中的两个 "places" 需要保证在同一时刻的任何事物的唯一 ID,相隔不到一毫秒,甚至可能在同一纳秒......这个唯一 ID要求生产速度极快,效率高,以可以有效解析的逻辑方式构建,可以容纳在 64 位以内,并且生成它的方法需要能够处理大量 ID 超过许多人的一生。这意味着他们无法通过数据库查找来获取尚未使用的唯一 ID,无法在生成 ID 后验证生成的 ID 是否是唯一的,并且他们无法使用可能生成重复项的现有方法,即使很少喜欢 UUID。于是他们想出了一个办法..

他们设置了一个自定义的共同纪元,比如今天以一个长整数作为基点。因此,他们有一个 42 位长的整数,从那个纪元开始从 0+time 开始。

然后他们还添加了一个序列作为 12 位长整数,以防单台机器上的单个进程必须在同一毫秒内生成 2 个或更多 ID。现在他们使用了 42+12=54 位,当你考虑到多台机器上的多个进程时(通常每个数据中心只有一台机器提供 ID,但可能更多,通常每台机器只有一个 worker/process)你意识到你需要的不仅仅是 42+12..

因此他们还必须对数据中心 ID 和 "worker"(进程)ID 进行编码。这将涵盖多个数据中心,每个数据中心有多个工作人员。这两个 ID 都是 5 位长的整数。所有这些整数都是无符号的,因此这些 5 位整数最多可以达到 31,这为每个部分 ID 提供了 32 种可能性,包括 0。因此,32 个数据中心,每个数据中心最多有 32 个工作人员。所以现在我们在42+12+5+5=64bits,最多有32x32=1024个worker产生这些ID分布。

所以.. 42 位部分的寿命长达 139 年...节点 ID(或数据中心 + 工作人员 ID)的 10 位...12 位序列(每个工作人员每毫秒 4096 个 ID)...你想出了一个最大 64 个保证的唯一 ID system/formula,它在 139 年的时间里扩展得非常好,它不以任何方式依赖数据库,但可以高效地生产并存储在数据库中。

所以,这个 ID 系统计算出 42+12+10,你可以将这 10 位分开,也可以不分开,但是你喜欢并且不要超出在任何地方存储 64 位无符号长整数。非常灵活,效果很好。

同样,它被称为 Snowflake ID,Twitter 想出了它。这 10 位可以称为分片 ID、节点 ID,或数据中心 ID 和工作人员 ID 的组合,这完全取决于您的需要。但是,通过不将 shard/node ID 绑定到用户而是绑定到多个进程并能够跨多个 "things" 使用该 ID,您将不必担心很多事情,并且可以跨越多个数据库充满了很多东西和..

唯一重要的是 shard/node ID 只能包含 1024 个不同的值,没有用户 ID 或他们可以使用的任何唯一 ID 只会从 0 到 1023 '将它自己分配给任何东西。

所以你看,那 10 位 某种东西 ,无论如何,它们都是静态的、可分配的并且很容易解析。

这是一个简单的 python 函数,它将生成一个雪花 ID:

def genSnowflakeId(worker_id, data_center_id, ids_generated):
    "Returns a snowflake ID - This function will generate a unique ID that fits in a 64 bit unsigned number that scales for multiple workers running in mutiple datacenters. You must manage a timestamp and sequence sanity with ids_generated (i.e. increment if time apart < 1 millisecond or always increment and roll over to 0 if > 4095). Ultimately this will allow you to efficiently generate unique IDs across multiple locations for 139 years that fits in a bigint(20) database field and can be parsed for the created timestamp, worker ID, and datacenter ID. See https://github.com/twitter-archive/snowflake/tree/snowflake-2010"

    import sys
    import time

    # Mon Jul  8 05:07:56 EDT 2019
    twepoch = 1562576876131L

    sequence = 0L
    worker_id_bits = 5L
    data_center_id_bits = 5L
    sequence_bits = 12L
    timestamp_bits = 42L
    #total bits 64

    max_worker_id = -1L ^ (-1L << worker_id_bits)
    max_data_center_id = -1L ^ (-1L << data_center_id_bits)
    max_ids_generated = -1L ^ (-1L << sequence_bits)

    worker_id_shift = sequence_bits
    data_center_id_shift = sequence_bits + worker_id_bits
    timestamp_left_shift = sequence_bits + worker_id_bits + data_center_id_bits
    sequence_mask = -1L ^ (-1L << sequence_bits)


    # Sanity checks for input
    if worker_id > max_worker_id or worker_id < 0:
        raise ValueError("worker_id", "worker id can't be greater than %i or less than 0" % max_worker_id)
    if data_center_id > max_data_center_id or data_center_id < 0:
        raise ValueError("data_center_id", "data center id can't be greater than %i or less than 0" % max_data_center_id)
    if ids_generated > max_ids_generated or ids_generated < 0:
        raise ValueError("ids_generated", "ids generated can't be greater than %i or less than 0" % max_ids_generated)

    timestamp = long(int(time.time() * 1000))

    new_id = ((timestamp - twepoch) << timestamp_left_shift) | (data_center_id << data_center_id_shift) | (worker_id << worker_id_shift) | sequence

    return new_id

希望这个回答能让你满意:)