Hibernate--Increment和Hilo主键生成策略原理

最近项目中遇到集群问题,比如我们有两个集群节点,在正常情况下只有一个节点工作(A),当出现异常时切换到另一个集群节点(B)上。项目中使用Hibernate的increment作为数据库主键生成策略。它的原理如下:
Hibernate初始化完成后,当获取主键时,会查询一次数据库将最大的Id查询出来,之后的操作就全部是在内存中维护主键的自增,保存时更新到数据库,其源码如下:


大家请看其generate方法,当sql语句不等于Null的时候,获取下一个版本,也就是从数据库拿最大的id,然后就将sql置为null。这样下次获取id时,就不会从数据库拿,而是在内存++。那么这样就会产生问题。比如系统现在在A节点上工作,当next走到10的时候,突然网络断了,于是系统切换到了B节点上工作,B节点获取id时查询数据库拿到最大值,并且顺利执行next走到了20,此时又切回到了A节点,然而A节点的next此时为10,并且sql语句为null,于是A不查询数据库直接取next的值,那么将导致从10开始到20的id都会发生主键冲突,必须重启A节点才能解决问题。

因此如果涉及到使用hibernate的集群一定不能使用increment做为主键生成策略。从上面分析我们可以看出如果想解决该问题,必须解决两个进程之间的内存共享,也就是共享next变量,但是实现起来很复杂。最后采用Hilo的主键生成策略解决。其部分源码如下:


上面代码说明获取主键时,hibernate都会在同一个事务中从数据库中拿出主键,并将该主键更新。这样保证另外的进程从数据库取值时能够获取最大值。

关键是下面的代码:


我们可以看到查询主键时将数据库的的主键+1保存,如果这个事物没有成功,那么this.lo将永远大于this.maxLo,即便是此时切换到了B,当再次回到A时,首先会查询一次数据库,这样保证this.hi永远不会相等。如果成功那么当从B切换到A时,由于B的hi值比A的hi值至少大this.maxLo+1,因此,即便A保持hi不变从内从中拿低位,也不会和B相同,因为当this.lo大于this.maxLo时又会查询数据库。这个算法太美了。

其详细原理可以参照源码和下面的链接对比:

http://hi.baidu.com/sai5d/blog/item/88e5f4db09e90277d0164e30.html

你可能感兴趣的:(Hibernate)