【问题标题】:Short user-friendly ID for mongomongo 的简短用户友好 ID
【发布时间】:2015-05-20 15:51:03
【问题描述】:

我正在创建一个实时股票交易系统,并希望为用户提供一种可读、用户友好的方式来参考他们的订单。例如,ID 应该是 8 个字符长,并且只包含大写字符,例如Z9CFL8BA。由于显而易见的原因,id 在系统中必须是唯一的。

我使用 MongoDB 作为后端数据库,并评估了以下不符合我要求的项目。

hashids.org - 这看起来不错,但它生成的 id 太长:

var mongoId = '507f191e810c19729de860ea';
var id = hashids.encodeHex(mongoId);
console.log(id)

导致:1E6Y3Y4D7RGYHQ7Z3XVM4NNM

github.com/dylang/shortid - 这要求您指定 64 个字符的字母表,并且如前所述,我只想使用大写字符。

我知道,实现我正在寻找的唯一方法很可能是生成满足我要求的随机代码,然后检查数据库是否存在冲突。如果是这种情况,在 nodejs / mongodb 环境中执行此操作的最有效方法是什么?

【问题讨论】:

    标签: node.js mongodb


    【解决方案1】:

    您正在尝试将 base-16(十六进制)转换为 base-36(字母表中的 26 个字符加上 10 个数字)。一种简单的方法可能是简单地使用parseInt 的radix 参数来解析十六进制id,然后调用.toString(36) 将其转换为base-36。这会将“507f191e810c19729de860ea”变成“VDFGUZEA49X1V50356”,将长度从 24 个字符减少到 18 个字符。

    function toBase36(id) {
      var half = Math.floor(id.length / 2);
      var first = id.slice(0, half);
      var second = id.slice(half);
      return parseInt(first, 16).toString(36).toUpperCase()
           + parseInt(second, 16).toString(36).toUpperCase();
    }
    

    function toBase36(id) {
      var half = Math.floor(id.length / 2);
      var first = id.slice(0, half);
      var second = id.slice(half);
      return parseInt(first, 16).toString(36).toUpperCase()
           + parseInt(second, 16).toString(36).toUpperCase();
    }
    
    // Ignore everything below (for demo only)
    function convert(e){ if (e.target.value.length % 2 === 0) base36.value = toBase36(e.target.value) }
    var base36 = document.getElementById('base36');
    var hex = document.getElementById('hex');
    document.getElementById('hex').addEventListener('input', convert, false);
    convert({ target: { value: hex.value } });
    input { font-family: monospace; width: 15em; }
    <input id="hex" value="507f191e810c19729de860ea">
    <input id="base36" readonly>

    【讨论】:

    • 感谢您的回复和代码。但是 18 个字符仍然有点太长了。我需要大约 8 个字符长的东西。
    【解决方案2】:

    我知道,实现我正在寻找的唯一方法很可能是生成满足我要求的随机代码,然后检查数据库是否存在冲突。如果是这种情况,在 nodejs / mongodb 环境中执行此操作的最有效方法是什么?

    根据您的描述,您使用 [0-9A-Z] 范围内的 8 个字符作为“id”。那是 36⁸ 组合 (≈ 2.8211099E12)。假设您的交易系统在中短期内没有获得疯狂的巨大人气,那么发生冲突的可能性就相当低。

    因此,您可以采取乐观的方法,生成一个随机 id,其中包含以下代码行中的内容(正如 @idbehold 在评论中所注意到的那样,请注意 Math.random 可能不够随机,因此可能会增加碰撞的可能性——如果你这样做,也许你应该调查better random generator [1])

    > rid = Math.floor(Math.random()*Math.pow(36, 8))
    > rid.toString(36).toUpperCase()
    30W13SW
    

    然后,在该字段上使用适当的唯一索引,您只需循环,重新生成一个新的随机 ID,直到在尝试插入新事务时没有冲突。由于碰撞的机会相对较小,因此应该终止。 大部分时间这将在第一次迭代时插入新文档,因为没有冲突。

    如果我没记错的话,假设有 100 亿笔交易,你在第一轮的碰撞几率仍然只有 0.3%,第二轮的碰撞几率略高于 0.001%


    [1] 在节点上,您可能更喜欢使用 crypto.pseudoRandomBytes 来生成随机 id。你可能会围绕它构建一些东西,也许:

    > b = crypto.pseudoRandomBytes(6)
    <SlowBuffer d3 9a 19 fe 08 e2>
    > rid = b.readUInt32BE(0)*65536 + b.readUInt16BE(4)
    232658814503138
    > rid.toString(36).substr(0,8).toUpperCase()
    '2AGXZF2Z'
    

    【讨论】:

    • 警惕Math.random(),因为它不够随机,与更好的随机数生成器相比,肯定会增加碰撞的机会。
    • @idbehold 是的,这是正确的。也许你知道 JavaScript 上有更好的随机生成器吗?
    • 在节点上你可以使用crypto.randomBytes()
    • @idbehold 谢谢你的建议。在这种情况下,crypto.pseudoRandomBytes 可能是更好的选择,因为我们不需要加密强的伪随机数据——如果熵池耗尽,OP 可能想要阻止。
    • 感谢大家的回复——您对如何在多线程环境中处理冲突解决过程有什么建议吗?我知道随着时间的推移,这将变得越来越低性能,并且已经使用的 id 表变得越来越大。在 mongodb 中实现这一点的最佳方法是什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-11
    相关资源
    最近更新 更多