我们有一个6节点的Red Hat 4.4.7/Linux 2.6.32网络,每个节点运行一个Java应用程序,该应用程序使用Hibernate 3.3.2.GA在中央Oracle数据库中创建记录。

我们遇到了一个问题,其中Hibernate生成重复的UUID。

所讨论的Java类定义如下:

@Entity
@Table(name = "X_Y")
@GenericGenerator(name = "x-y-uuid", strategy = "uuid")
public class XY implements ... {
    @Id
    @Column(name = "X_Y_ID")
    @GeneratedValue(generator = "x-y-uuid")
    private String id;
    ...
}

使用我们已经成功使用了一段时间的这个定义,我们遇到了重复的X_Y_ID密钥的问题。我们禁用了X_Y_ID的唯一约束,然后重新运行该过程。同时,我们开始在代码以及Hibernate代码中挖掘可能的错误。阅读Hibernate的UUIDHexGenerator,似乎UUID的前8个字符是基于机器IP地址的,而后8个字符是基于JVM启动时间的。

完成对X_Y_ID禁用唯一约束的过程后,我们对所得的UUID进行了一些分析。我们发现实际上有59个重复的X_Y_ID值。
令我们惊讶的是,查询:
select SUBSTR(X_Y_ID,1,8), COUNT(*)
from X_Y
group by SUBSTR(X_Y_ID,1,8)

表示所有6台机器的前8个字符都相同。查询:
select SUBSTR(X_Y_ID,9,8), COUNT(*)
from X_Y
group by SUBSTR(X_Y_ID,9,8)

给了
"49d99de6"  2148309
"49d99e3c"  2044966
"49d99def"  2228095
"49d99df2"  2091068
"49d99dee"  4110661

如您所见,有5行,最后一行大约是行数的两倍。这本身并不奇怪。 (这意味着在两台不同机器上的JVM彼此之间的启动时间为256ms)。

进一步的调查显示,为前八个字符ff808081生成的值对应于IP地址127.0.0.1(本地主机)。

在其中一台计算机上运行ifconfig可以给出(例如):
eth0      Link encap:Ethernet  HWaddr 00:50:56:81:2C:20
          inet addr:10.191.8.50  Bcast:10.191.63.255  Mask:255.255.192.0
          inet6 addr: fe80::250:56ff:fe81:2c20/64 Scope:Link
          ...

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          ...

我的问题是:
  • Hibernate看到的IP地址怎么可能是127.0.0.1,而不是10.191.8.50?
  • 我们如何才能在已部署的系统上防止这种情况发生?
  • 最佳答案

    正如@thatotherguy在评论中指出的那样,AbstractUUIDGeneratorUUIDHexGenerator的Hibernate实现与RFC-4122兼容还很遥远。在仔细研究之前,我从未真正意识到过实现的糟糕程度。

    顺便说一句,以其实现本身为基础,此处问题的根本原因归结为 UUIDHexGenerator s使用 InetAddress.getLocalHost() (通过AbstractUUIDGenerator)来得出“唯一”值。如果对主机名的名称查找结果为127.0.0.1(例如,您的/etc/hosts文件中的主机名),或者主机名是“local”,则将使用它。

    您有几种选择:

  • 如果可以的话,您可以更新/etc/hosts以包含主机名的LAN IP。不过,您将不会使用正确的UUID(与下一点的最后一部分相同)。
  • 如果Hibernate的算法不足,则可以定义自定义 IdentifierGenerator 并提供更适合您的任务的更好的UUID生成算法。我将以Java内置的 UUID 为基础,它是兼容的。但是,您可以通过扩展 UUIDHexGenerator 并覆盖protected int getIP()以返回准确的IP地址来“破解”它。这是由于 AbstractUUIDGenerator s implementation(您的getIP()将不再返回其IP实例字段的值)而引起的黑客攻击,因为它仍然不是正确的UUID。可能就足够了,但我不建议这样做。
  • 无需使用生成器,而是指定手动ID分配,然后自己生成UUID。同样,Java的UUID在这里可以为您工作。
  • 有一个更新的UUID生成器策略“uuid2”,它使用 UUIDGenerator 。它是3.6中的新功能,而3.3.2中不可用。的来源is available。我以前没有使用过这种策略,也不能为它辩护。但是,正如安德鲁·斯坦(Andrew Stein)在下面的评论中观察到的那样,对源的检查显示它为provides a strategy built around Java's UUID ,这很可能是一个不错的选择,并且肯定比旧的AbstractUUIDGenerator派生的变体更好。

  • 如果适用,选项1是最简单的快速修复方法,但是可能存在维护/部署问题,并且实际上不会生成格式正确的UUID。从长远来看,使用UUID的选项2(或具有适当策略的选项4)可能是最正确的。

    有一个article describing various UUID assignment strategies for Hibernate,其中可能包含一些更有用的见解和示例。

    10-05 17:51