2014-11-24 77 views
3

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

我们遇到了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生成的值对应于本地主机127.0.0.1的IP地址。

这些机器上的一个运行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?
  • 我们可以做些什么来防止在部署系统上发生这种情况?
+0

这显然是Hibernate中的一个bug,应该修正它们。但是,'grep $ HOSTNAME/etc/hosts'并查看是否有条目'127.0.0.1 yourhostname' – 2014-11-24 17:54:02

+2

@JasonC为什么Hibernate没有生成不通用且不唯一的UUID?如果他们遵循[RFC](http://www.ietf.org/rfc/rfc4122.txt),而不是自行滚动,则不会发生这种情况。 – 2014-11-24 18:10:52

+0

@thatotherguy是的,实际上,更仔细地看待'UUIDHexGenerator',这是一个相当差的实现。我从来没有真正意识到它是如何脱落的......我将重新回答我的答案,以反映这一点。 – 2014-11-24 18:36:51

回答

2

由于@thatotherguy在评论中指出,Hibernate实现的AbstractUUIDGeneratorUUIDHexGenerator是相当远离被RFC-4122标准。在仔细研究之前,我从未真正意识到实施过程有多糟糕。

除此之外,将其实施的原因归结为UUIDHexGenerators使用InetAddress.getLocalHost()(通过AbstractUUIDGenerator)提出了一个“唯一”值。如果您的主机名的名称查找结果为127.0.0.1(例如,它在您的/etc/hosts文件中),或者主机名是“本地”,则它将使用该名称。

您有几种选择:

  1. 您可以更新/etc/hosts包含局域网IP为您的主机名,如果这是一个选项。不过,你不会使用适当的UUID(与下一点的最后部分相同)。

  2. 如果Hibernate的算法不足,您可以定义一个自定义的IdentifierGenerator并提供更适合您的任务的更好的UUID生成算法。我将基于Java的内置UUID这是合规的。然而,你可能可能“破解”它通过扩展UUIDHexGenerator和覆盖protected int getIP()返回一个准确的IP地址。这是因为AbstractUUIDGenerators implementation(你的getIP()将不再返回它的IP实例字段的值),因为它仍然不是一个适当的UUID。这可能就足够了,但我不推荐它。

  3. 而不是使用生成器,指定手动ID分配,并自己生成UUID。再次,Java的UUID可以在这里为你工作。

  4. 有一个较新的UUID生成器策略“uuid2”,它使用UUIDGenerator。它在3.6中是新的,在3.3.2中不可用。它的来源is available。我之前没有使用过这个策略,也不能说出来;然而,正如Andrew Stein在下面的评论中指出的那样,对来源的检查显示它为provides a strategy built around Java's UUID,这可能是一个很好的选择,并且肯定比旧版AbstractUUIDGenerator衍生的变体更好。

选项1是最简单的快速修复程序,如果它适用于您,但可能有维护/部署问题,并且也不会真正生成格式正确的UUID。从长远来看,选项2使用UUID(或选项4与适当的策略)可能是最正确的。

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

+0

嗨@JasonC,非常感谢您的建议。感兴趣的一点是,“uuid2”使用的默认'UUIDGenerator'实际上可以归结为Java的UUID,正如源代码所示:https://github.com/hibernate/hibernate-orm/blob/master /hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java – 2014-11-24 22:01:52

+0

@AndrewStein啊,很高兴知道。这对他们来说是正确的,我很高兴他们走上了这条路。 – 2014-11-24 22:09:21