【问题标题】:Is there an issue with this UUID generation code?这个 UUID 生成代码有问题吗?
【发布时间】:2012-09-07 17:46:38
【问题描述】:

所以我有一些代码需要使用 UUID 作为数据库 ID。为简单起见,我使用了 v4(随机),而且我看不出有任何真正的理由使用任何其他不那么随机的 UUID 版本。我的 UUID 类的定义大致如下(简化):

class uuid {
public:
    static uuid create_v4();
public:
    // cut out for simplification...
public:
    uint8_t bytes[16];
};

实际的生成代码如下所示:

namespace {

uint32_t rand32() {
    // we need to do this, because there is no
    // gaurantee that RAND_MAX is >= 0xffffffff
    // in fact, it is LIKELY to be 0x7fffffff
    const uint32_t r1 = rand() & 0x0ff;
    const uint32_t r2 = rand() & 0xfff;
    const uint32_t r3 = rand() & 0xfff;
    return (r3 << 20) | (r2 << 8) | r1;

}

}

uuid uuid::create_v4() {

    static const uint16_t c[] = {
        0x8000,
        0x9000,
        0xa000,
        0xb000,
    };

    uuid uuid;

    const uint32_t rand_1 = (rand32() & 0xffffffff);
    const uint32_t rand_2 = (rand32() & 0xffff0fff) | 0x4000;
    const uint32_t rand_3 = (rand32() & 0xffff0fff) | c[rand() & 0x03];
    const uint32_t rand_4 = (rand32() & 0xffffffff);

    uuid.bytes[0x00] = (rand_1 >> 24) & 0xff;
    uuid.bytes[0x01] = (rand_1 >> 16) & 0xff;
    uuid.bytes[0x02] = (rand_1 >> 8 ) & 0xff;
    uuid.bytes[0x03] = (rand_1      ) & 0xff;

    uuid.bytes[0x04] = (rand_2 >> 24) & 0xff;
    uuid.bytes[0x05] = (rand_2 >> 16) & 0xff;
    uuid.bytes[0x06] = (rand_2 >> 8 ) & 0xff;
    uuid.bytes[0x07] = (rand_2      ) & 0xff;

    uuid.bytes[0x08] = (rand_3 >> 24) & 0xff;
    uuid.bytes[0x09] = (rand_3 >> 16) & 0xff;
    uuid.bytes[0x0a] = (rand_3 >> 8 ) & 0xff;
    uuid.bytes[0x0b] = (rand_3      ) & 0xff;

    uuid.bytes[0x0c] = (rand_4 >> 24) & 0xff;
    uuid.bytes[0x0d] = (rand_4 >> 16) & 0xff;
    uuid.bytes[0x0e] = (rand_4 >> 8 ) & 0xff;
    uuid.bytes[0x0f] = (rand_4      ) & 0xff;

    return uuid;
}

看起来对我来说是正确的,但我最近从数据库中收到一个错误,说我尝试插入的 UUID 是重复的。由于这应该是极不可能的,我不得不假设我的代码可能存在问题。所以有人看到有什么不对吗?我的随机 UUID 生成是否不够随机?

注意:我不能使用 boost 的随机数生成或它的 UUID 库。我希望可以,但我与安装了特定版本库的特定系统绑定在一起,并且几乎不可能获得足够新的 boost 版本来拥有这些功能。

【问题讨论】:

  • 你怎么播rand()
  • 对我来说使用普通的rand() 似乎不是一个好主意,特别是因为 LCG 生成的数字的低位是最少 生成数字的随机部分(它们通常具有较短的周期)。你应该至少取一些高阶位,如果我是你,我会选择包含时间戳的算法版本,至少你不太可能生成重复。
  • 可以让数据库自己生成吗?那将是最安全的选择。
  • @Matteo:据我了解,gnu libc 对rand() 的实现采取措施避免 LCG 具有较低的随机性,尽管我可能是错的。
  • 使用time() 播种的分辨率为 1 秒。您能否在同一秒内多次运行种子代码?

标签: c++ random uuid


【解决方案1】:

代码对我来说似乎是合理的。正如 cmets 中提到的,对于 rand() 是否是该任务的好选择存在一些问题,但是假设正在使用更新版本的库,您对它的使用似乎是生成 32 位数据的合理方法用于确保低位与高位一样随机(您在 cmets 中也提到过)。

因此,只要 rand() 函数做得还不错,您似乎不太可能得到重复。所以我的猜测是有另一种失败。想到的一些可能性:

  1. 时间(0) 失败。这似乎极不可能。如果它返回 -1 以指示在两次不同的运行中出现错误,那么它可能会导致问题。但是,它应该能够失败的唯一方法是给它一个无效的地址(这里绝对不是这种情况)。
  2. 多线程使用。我不认为 rand() 是线程安全的。如果在多线程情况下使用此代码,可能会导致意外行为。
  3. Cron 造成困难。如果工作站上的时钟不准确并且它被自动设置(例如,通过 rdate)与某个服务器同步,那么它可能会导致 cron 作业在某个时间重复。我能够简单地通过创建一个 cron 作业来模仿这种行为,每分钟将当前日期转储到一个文件中,然后重复设置日期......它最终将相同的日期/时间(到第二个)写入文件更多不止一次。对于时间函数的一秒分辨率,这很容易导致重复种子。
  4. 将 UUID 写入数据库的代码不正确。即使 UUID 生成器运行良好,也可能存在将相同 UUID 两次写入数据库的不同错误。

只是疯狂的猜测。其中,第三个是我最喜欢的,但如果我正在审查自己的代码,我会首先怀疑第四个。

【讨论】:

  • #3 非常有趣,我一定要检查一下。我已经更新了代码,以便在可以成功读取种子时使用/dev/urandom,如果失败则回退到time(0)。到目前为止,它看起来不错。
  • @Evan:上周五下午,在等待一些代码扫描完成时(这让我失去了生命和灵魂),我在 Windows 和 Linux 中使用了你的 UUID 生成例程。我只是生成了 1000 万个,将它们转储到一个文件中,排序,然后通过 awk 或 ruby​​ 查找重复项(现在不记得了)并且没有产生重复项。并不是说那个测试真的能证明什么,但我没有发现你代码中的逻辑有什么问题。
  • 感谢您有兴趣深入了解这一点。代码已经运行了几个星期了,我只得到了一次重复键错误。所以我猜这真的可能只是运气不好。
  • 我接受这个答案,因为我认为问题出在第 3 点。我更改了代码以使用/dev/random 查看,并且仅在失败时使用time(0)。从那以后再也没有发生过碰撞。再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多