【发布时间】:2017-10-30 05:37:56
【问题描述】:
问题
我正在寻找有关本地唯一的 GUID 替代方案的反馈,并满足以下要求:
- 发生碰撞的几率非常低(以至于我们宁愿每年碰撞一次也不愿进行检查)
- 不会泄露敏感信息,例如存在多少项
- 在 SQL 数据库中具有高性能
- 可复制/粘贴以供手动查询(作为查询字符串和查询结果)
- 无需编码即可用作 URI 组件
为了满足要求,我决定采用64位无符号整数的形式。它在 CPU 上很容易,主键使用美观且小巧,半人类可读,仅数字,并且在手动查询时易于复制/粘贴。 (作为反例,BLOB 严重阻碍了大多数 SQL 数据库的手动查询。)
此外,Percona demonstrates 单调递增的值作为主键的性能要好得多,尤其是在插入速度方面,因此这是一个目标。
建议的结构
从左到右,最重要的位在左边
- 46 位。 时间戳。 Unix 时间,以毫秒为单位。 (至少在 C# 中,亚毫秒级的时间并不容易获得。)这将持续到 4199 年的某个地方。它为我们提供了单调递增的值。
- 8 位。 本地 IP 的一部分。机器内部 IP 地址的最后一个组成部分,是最快的可用网络接口。大多数服务器应该是以太网 LAN。
- 10 位。 唯一性。一个静态计数器,在使用时递增(互锁),带有环绕。
碰撞
任何时候都有 1/1024 (~0.1%) 的碰撞几率:
- 两个系统共享相同的最后一个 IP 地址组件并且在相同的毫秒内进行呼叫。 这是完全可以避免的。
- 系统时钟被调回并且它在时间更改前的同一毫秒内进行调用。 这应该是符合要求的极少数情况。
限制
有趣的是,我们似乎满足了要求(#2 是一个狡猾的要求)。让我们来看看其中的一些限制。
- 必须仔细维护服务器的本地 IP 地址 - 即使在不同的数据中心之间(如果适用)。
- 我们不能支持超过 255 台服务器 - 如果 IP 存在其他限制,可能会更少。
- 我们泄露了有关哪些标识符是由同一服务器创建的信息。不过,我相信许多 GUID 实现也是如此。
- 可以通过检查用户自己的请求之间的计数器增量来获取有关流量的信息。由于计数器用于各种类型的数据,并且以难以归因于任何特定类型的数据的方式快速增加,因此效率会降低。
- 标识符比具有大量随机性的标识符更容易猜测。蛮力攻击每尝试毫秒需要大约 512 次调用(唯一性)。理想情况下,这种攻击不会产生任何结果,即无论标识符是不存在还是不属于用户,系统都会报告“未经授权”,并且可以抵抗定时攻击。实际上,我们假设专门的攻击者会发现漏洞。
注意事项
限制 #1 和 #2 必须适合公司。
限制 #3 似乎在现有 GUID 实现中被认为是可以接受的,并且是我愿意接受的。
限制 #4 是一个棘手的限制。这些信息有多敏感? “所以我们每分钟插入 10K 次,插入到未知数量的表中。”相对交易量确实提供了更多洞察力:“在 08:00-09:00 之间,活动量是一个小时前的两倍。”尽管如此,这通常是给定领域的常识。意外的峰值可能会泄漏更多信息。 “所以系统在凌晨03:00努力工作。”这一切有多糟糕?从公开自动增量标识符的公司数量来看,我们可能会说这是一种改进,但它可能会破坏交易。
我们可以使用(加密)随机位作为唯一性来处理限制 #4,但这会引入第三次冲突机会:每当系统在一毫秒内生成多个标识符时。生日悖论在那里尤其成问题。
如果我们允许时间戳在 2527 中回绕,我们可以释放 2 位。自私和对后代不敏感,或者傲慢地假设我们的代码会被使用更长时间? :-)
还有什么?
我欢迎您的反馈、改进、想法和我错过的限制!你会如何解决这个问题?
【问题讨论】:
-
更新:为了确保服务器或应用程序实例之间的唯一性,我做了一个实现,其静态初始化器尝试从 config 中提取服务器标识符,用作中间 8位。如果失败,它会退回到检查最快的网络适配器 IP(即 LAN IP)的最后一部分。最后,它恢复为使用 0,这实际上仅用于单元测试。
-
更新:45 位时间戳就足够了。这允许使用有符号整数而不会得到负数(是的,你,SQL Server)。所以我们只触及 63 个最低有效位。
标签: indexing primary-key collision guid identifier