什么是高/低算法?


464

什么是高/低算法?

我已经在NHibernate文档中找到了这一点(这是生成唯一密钥的一种方法,第5.1.4.2节),但是我没有找到有关其工作原理的很好的解释。

我知道Nhibernate可以处理它,并且我不需要了解内部,但是我很好奇。

Answers:


540

基本思想是,您有两个数字组成主键-“高”数字和“低”数字。客户端可以从本质上增加“高”序列,知道它随后可以安全地从先前的“高”值的整个范围(具有各种“低”值)生成密钥。

例如,假设您有一个“高”序列,当前值为35,而“低”数在0-1023范围内。然后客户端可以将序列增加到36(其他客户端在使用35时能够生成密钥),并知道密钥35 / 0、35 / 1、35 / 2、35 / 3 ... 35/1023是全部可用。

能够在客户端设置主键,而不是在没有主键的情况下插入值然后将其取回客户端的功能非常有用(尤其是在ORM中)。除了其他方面,这意味着您可以轻松建立父子关系,并在进行任何插入操作之前就将所有密钥都放置到位,这使批处理更为简单。


14
您是说“低范围”在客户端内部协调,而“高顺序”对应于数据库顺序吗?
克里斯·诺

14
然后,通常将hi&lo值组成一个整数值,还是分为两部分的业务键?
克里斯·诺

51
就像IP地址一样-ICANN为您提供一个较高的“网络”编号,然后在给定的CIDR范围内,您将拥有尽可能低的“主机”编号。
gbjbaanb

6
@Adam:从根本上讲,什么都没有-增加一个值(“高”部分)可能比生成一堆密钥便宜得多。(这是可能在数据传输方面更便宜-你可以“保留”一个巨大的最小带宽键的数量。)
乔恩斯基特

4
@亚当:如果键只是数字,那是真的。对于GUID而言,不是很多:)但是,是的,对于简单的数字,任何原子的“固定数量的增量”都可以。如果您将它看作一个数字分为两部分,那实际上就是hi-lo所做的事情。
乔恩·斯基特


34

由于这是一个非常常见的问题,因此我写了这篇文章,此答案基于该文章

高/低算法将序列域分为“高”组。同步分配一个“ hi”值。每个“ hi”组都有最大数量的“ lo”条目,可以通过离线分配它们,而不必担心并发重复的条目。

  1. “ hi”令牌是由数据库分配的,并且保证两个并发调用可以看到唯一的连续值
  2. 一旦检索到“ hi”令牌,我们只需要“ incrementSize”(“ lo”条目的数量)
  3. 标识符范围由以下公式给出:

    [(hi -1) * incrementSize) + 1, (hi * incrementSize) + 1)

    而“ lo”值将在以下范围内:

    [0, incrementSize)

    从以下值开始应用:

    [(hi -1) * incrementSize) + 1)
  4. 使用所有“ lo”值时,将获取新的“ hi”值,并且循环继续

您可以在本文中找到更详细的解释:

这个视觉演示也很容易遵循:

在此处输入图片说明

虽然hi / lo优化器可以优化标识符生成,但是在不了解标识符策略的情况下,它不能与其他将行插入数据库的系统配合使用。

Hibernate提供了pool-lo优化器,它提供了hi / lo生成器策略的优点,同时还提供了与其他不了解此序列分配策略的第三方客户端的互操作性。

与传统的高/低标识符策略相比,池低优化器既高效又可与其他系统互操作,是更好的选择。


我有时真的不明白你是不是哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈一个?)在我们的数据库中插入行(标识符生成器也不会用来插入行吗?),而对标识符策略一无所知。
阿德林

其他系统,例如试图运行INSERT语句的DBA。如果她读取当前序列数据,您是否知道我们在此特定数据库表中使用hilo,就很容易找出下一个标识符值?
Vlad Mihalcea '17

如果评论不适合您的回答,我深表歉意,但我想知道默认情况下使用了哪种优化程序?还是取决于数据库(我使用的是PostgreSQL)?因为我无法弄清楚当前序列值和生成的ID之间的关系。我正在使用@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "name") @SequenceGenerator(name="name", sequenceName = "name_seq", allocationSize=100)我的ID。
StefanGolubović,

1
从Hibernate 5开始,Pooled是新的Optimizer,而不是Hi / lo。请查看本文以获取有关Pooled Optimizer的更多详细信息
弗拉德·米哈尔恰

@VladMihalcea,我相信您在第三个项目符号中有错别字,第一个代码段在, (hi * incrementSize) + 1)……应该是, hi * incrementSize)吧?
Huiagan

23

Lo是一个缓存的分配器,通常根据某些机器字的大小,而不是人类可能会明智选择的有意义的大小范围(例如,一次获取200个密钥),将密钥空间分成大块。

高低使用往往会在服务器重新启动时浪费大量密钥,并会生成大量的人为不友好的密钥值。

比Hi-Lo分配器更好的是“线性块”分配器。这使用了类似的基于表的原理,但是分配了大小合适的小块并生成了对人类友好的值。

create table KEY_ALLOC (
    SEQ varchar(32) not null,
    NEXT bigint not null,
    primary key (SEQ)
);

要分配下一个密钥,例如200个密钥(然后将其作为范围保存在服务器中并根据需要使用):

select NEXT from KEY_ALLOC where SEQ=?;
update KEY_ALLOC set NEXT=(old value+200) where SEQ=? and NEXT=(old value);

如果您可以提交此事务(使用重试来处理争用),则您已分配了200个密钥并可以根据需要分配它们。

由于只有20个块大小,因此该方案比从Oracle序列中分配的速度快10倍,并且可在所有数据库中100%移植。分配性能等同于高低。

与Ambler的想法不同,它将键空间视为连续的线性数字线。

这样可以避免使用组合键(从来都不是一个好主意),并且可以避免在服务器重新启动时浪费整个lo-word。它生成“友好的”,人类规模的键值。

相比之下,Ambler先生的想法是分配高16位或32位,并随着高字数的增加而生成较大的对人不友好的键值。

分配的密钥比较:

Linear_Chunk       Hi_Lo
100                65536
101                65537
102                65538
.. server restart
120                131072
121                131073
122                131073
.. server restart
140                196608

在设计方面,他的解决方案在数字行(复合键,大型hi_word产品)上从根本上比Linear_Chunk更为复杂,而没有获得任何比较优势。

Hi-Lo设计出现在OO映射和持久性的早期。如今,诸如Hibernate之类的持久性框架提供了更简单,更好的分配器作为其默认值。


4
帖子不错,但您没有回答这个问题。
orbfish

1
+1是一个有趣的答案。我同意绝大多数应用程序都无法从Hi-Lo中获得比简单方法更多的优势。但是,我认为Hi-Lo更适合于高度并发的应用程序中多个分配器的特殊情况。
richj 2014年

1
谢谢@richj!我的观点是,可以将多个分配器或较大的块大小与“线性块分配”一起使用,但是,与Hi / Lo不同,它保持分配器NEXT_VAL与表中键的线性对应关系,并且是可调整的。与HiLo不同,它不需要乘法-只是没有必要!NEXT_HI的乘数存储使得希洛更加复杂和休息可调性,因为改变块大小会随意改动将发行下一个关键。参见:literatejava.com/hibernate/...
托马斯W¯¯

2
我对多个独立的分配器感兴趣。使用Hi-Lo,显然可以将高值划分为分配器ID /块ID。(对我而言)尚不明显可以将相同的方法应用于线性块,但是在分配器之间划分总范围基本上是相同的问题。我知道了 谢谢。
richj

1
哦,考虑一下之后,我认为SEQ列映射到表名。例如,有一个分配器“客户”表,一个分配器“订单”表,依此类推。原谅我,有时候我很慢。
安东尼·约翰逊

1

根据我的经验,我发现Hi / Lo算法非常适合具有复制方案的多个数据库。想象一下。您在纽约有一个服务器(别名01),在洛杉矶有另一个服务器(别名02),那么您有一个PERSON表...因此在纽约,当创建一个人时,您始终将01用作HI值LO值是下一个安全对象。例子。

  • 010000010杰森
  • 010000011大卫
  • 010000012西奥

在洛杉矶,您始终使用HI02。例如:

  • 020000045鲁珀特
  • 020000046奥斯瓦尔德
  • 020000047马里奥

因此,当您使用数据库复制(无论使用什么品牌)时,所有主键和数据都可以轻松自然地组合在一起,而不必担心重复的主键,冲突等。

在这种情况下,这是最好的方法。


它在Hibernate中不起作用。HiLo algrotirm在每次交易中都会获得一个新的序列值,因此HI计数器会相应增加。但是在您的示例中,HI计数器对于一个DB始终是恒定的。
Dmitry1405 '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.