数据结构中的项的版本化的制作方法

文档序号:37231727发布日期:2024-03-05 15:43阅读:45来源:国知局
数据结构中的项的版本化的制作方法


背景技术:

1、诸如b树之类的树结构可以用作索引数据结构,用于存储键值对(每对包括一个值,即正在存储的内容,以及一个键,用于索引该值)。树的每个叶节点都包含多个项,这些项是键值对。从叶子向上的树,树的每个内部节点都包含其每个子节点(可以是叶子或其他内部节点)所包含的键值范围的指示。

2、当一个新项被添加到树中时,写入器使用树结构来确定哪个叶子包含新项的键范围。如果这个叶子没有满,新项可以添加到已有叶子中。然而,如果叶子已满(叶子和内部节点通常具有最大字节大小),那么叶子将被分裂。即创建一个新叶子。这也意味着更新旧叶子和新叶子的父节点,以便正确引用两个叶子的键范围。如果对父节点的更新也会使父节点超出其最大大小,那么父节点本身将被分裂,并更新祖父母的引用,依此类推。如果项被删除,这也可能涉及合并叶子或内部节点。

3、在给定的应用中,写入器或读取器可能需要执行许多操作。读取器可以读取具有特定键的单个项,或者可以执行范围扫描以从一系列键中读取。写入器可以写入新条目、修改已有条目或删除条目。这里的写入也可以称为更新的一种形式,因为它更新了树(不一定是已有项)。写入可以是将新项写入树,也可以是修改树中的已有项。更一般地,树的其他更新形式包括分裂节点和合并节点。

4、索引数据结构用于广泛的软件系统,例如数据存储和数据库、文件系统、操作系统等。索引数据结构存储键值对,并支持查找、扫描、插入和删除键值对等操作。b树就是这样一种索引数据结构。b树是按顺序索引,这意味着它也支持扫描操作。扫描操作返回存储在树中的所有键值对,其中键在指定范围内。


技术实现思路

1、如果读取器试图从树的一部分读取一组项,而写入器正在写入树的同一部分,则并发读取器和写入器可能会出现潜在问题(对于目前的目的,“并发”仅意味着时间重叠)。

2、例如,考虑一个场景,其中读取器正在执行范围扫描,同时数据也在扫描范围内写入。例如,想象一下要对给定的叶子执行两次写入:一个新的或修改的项,键为30,以及一个新的或修改的项,键为90(实际上键可能比这个大得多,但这只是为了说明)。写入是按照这个顺序执行的,并且需要有限的时间。现在想象一下,读取器也在同一时间对键为1-100的项进行扫描。按照这个顺序执行扫描,也需要有限的时间。由于扫描的方向(从1到100向上),读取器可能会拾取带有键90的后面项的写入,而不是带有键30的前面项的写入,尽管带有键30的项的写入可能会更早完成。例如。假设两个项的写入都在范围扫描开始后开始,但具有键30的项的写入直到扫描通过键30后才完成,而具有键90的项的写入在扫描到达键90时已经完成(另请注意,不同的写入操作不一定需要相同长度的时间来完成,例如,由于cpu资源的可用性不同)。在这种情况下,扫描将捕获具有键90的项的新写入值,但不会捕获具有键30的项的新写入值,尽管具有键90的项的写入开始较晚。或者作为另一个例子,假设具有键30的项的写入在扫描开始之前开始,但直到扫描通过键30之后才完成。如果扫描只是简单地忽略正在写入的项(例如,因为它们被写入器锁定),则扫描可以捕获具有键90而不是键30的项的写入。

3、如果读取器捕获的树的一部分的快照始终与写入该树的项的顺序一致,那将是可取的。类似的问题也可能出现在用于存储键值对的其他形式的数据结构中,而不仅仅是树结构。

4、一致性要求有时用“线性化”来表达。如果一组操作的效果发生时,就好像每个操作都是在时间上的瞬间执行的(而不是跨越有限的时间),则可以说它们是线性化的。这个瞬间必须在操作发出的时间和接收到其结果的时间之间。

5、一个简单的解决方案就是让每个操作锁定整个树。这对性能不利,但保证了强一致性。

6、更复杂的系统可能采用多版本并发控制(mvcc)。在传统的mvcc系统中,这种一致性是通过使用版本控制和让每个写入器对正在写入的项的内存位置放置锁来实现的。树具有单个全局版本号,每个项被分配一个单独的版本号。当写操作要写入一个项时,它通过将其单独的版本号设置为无效值来对要写入的项的内存位置放置锁。继而,写操作写入有问题的项,继而通过读取全局版本号来获取读取的版本号,继而增加它。继而,它通过将项的版本号设置为先前在同一写操作中读取的全局版本号(从增加之前)来释放锁。在读操作开始时(例如。范围扫描),读取器读取全局版本号,继而开始搜索指定范围内键的项。这样做它将读取任何版本号小于或等于它在读取操作开始时读取的全局版本号的未锁定项。如果读取操作在扫描范围内遇到锁定的项,它必须等待该项上的锁被释放才能完成。项是否可见取决于版本号。写入操作在执行结束时获取写入版本,因此即使它在读取之前开始,它也可能在读取器获取其读取版本之后获取写入版本。如果扫描范围内的写入操作在读取操作开始之前获取其写入版本,继而,该项将被读取操作读取,因为该项的版本号低于读取器在读取操作开始时读取的版本号。如果两个写入都开始但在范围扫描开始时未完成,则读取器必须等待两个锁才能完成范围扫描。另一方面,如果具有键30的项的写入在范围扫描开始之前开始但未完成,并且具有键90的项的写入在范围扫描开始之后开始,则扫描将捕获具有键30但不是90的项的写入,因为具有键90的项的版本号高于读取器在扫描操作开始时读取的版本号(尽管扫描仍然必须等待两个锁都被释放才能发现这一点)。这是因为写入在其执行结束时增加了版本。如果两个项的写入都是在范围扫描开始后开始的,但仍然在扫描期间,那么这个扫描也将能够看到对键30的更新,但不能看到对键90的更新,因为对键30的写入将设置为与扫描操作捕获的版本相同(尽管扫描仍然必须等待两个锁都被释放才能发现这一点)。所以扫描可能不会捕获所有的写入,但不存在它会捕获对键90的较晚写入而不捕获对键30的较早写入的场景。

7、然而,这种传统方案要求读取器在完成读取之前等待锁被释放。最好提供一种版本控制方案,解决一致性问题,而不使读取器等待锁。

8、根据本文公开的一个方面,提供了一种系统,其包括:存储器、写入器和读取器。所述存储器存储包括多个项的数据结构,每个项包括键值对。所述写入器被设置为执行多个写入操作,每个写入操作写入相应的项,或者新项被添加到所述数据结构中,或者已有项被修改在所述数据结构中。所述读取器被配置为执行组读取操作,以从所述数据结构中读取具有指定范围内的键的任何项。所述写入器被配置为维护全局写入版本,并且所述读取器被配置为维护全局读取版本。所述写入器被配置为通过以下方式执行每个写入操作:i)读取并且继而递增所述全局写入版本,ii)在所述数据结构中写入所述相应项,包括使用相应版本号对相应项进行版本化,该相应版本号等于在增量之前由相应的写入操作读取的全局写入版本,继而iii)生成指示相应版本号的释放指示符。写入器还被配置为向读取器发送对应于至少一些释放指示符的释放信号,每个释放信号用信号通知相应的版本号,但在生成指示所有较低版本号的释放指示符之前,不发送对应于指示更高相应版本号的任何释放指示符的释放信号。读取器被配置为,在接收到写入器发送的每个释放信号时,将全局读取版本更新为等于释放信号中指示的相应版本号。读取器被配置为通过以下方式执行组读取操作:在组读取操作开始时读取全局读取版本,继而基于具有等于或小于组读取操作开始时读取的全局读取版本的版本号,选择性地从数据结构中的指定范围的键中读取项,跳过(即,忽略)具有更大版本号的指定范围中的项。

9、因此,每个单独的项都被赋予了一个本地写版本号,而整个数据结构则与一个全局写版本号和一个单独的全局读版本号相关联(与传统方案中的单个全局版本号不同)。当要执行写操作时,写入器读取并增加全局写版本(最好是原子地),并使用值读取(从增量之前)作为其写版本。写操作使用其写版本对其创建的项进行版本转换。在完成之前,写操作必须释放其相应的更新。写入按版本顺序释放:当等待完成的写操作变成具有最低未释放(未提交)写版本时,它会使用自己的写版本更新全局读版本。每个读操作都会在读操作开始时观察全局的读版本,并将其用作自己的读版本。读取器只读取版本小于或等于其读版本的项,忽略其余部分。

10、再考虑一下键1-100的范围扫描和键30和90的两次写入的例子,前者在后者之前开始。读取器可以如下情况启动:i)在30和90被释放之前,或者ii)在30被释放但在90被释放之前,或者iii)在30和90都被释放之后。在情况i)中,读取器将忽略30和90,即使它们同时入,因为它们的版本大于其读取版本。在情况ii)中,读取器必须看到30,因为30只有在完全插入后才会被释放。如果它观察到90,它将忽略它,因为90的版本将大于读取器的读取版本。在情况iii)中,读取器将同时观察30和90,因为它们必须入才能被释放。

11、因为读取版本可以落后于最新的写入版本,并且因为读取器被配置为忽略版本高于其读取版本的项,所以读取器不会等待写入器当前正在更新的项。在先前的方案中发生了对读取锁定的需求,因为增加全局版本号和向读取器释放写入是同一件事——一旦后面的写入操作增加全局版本号,读取器就会直接跳转到尝试读取使用该最新版本号版本化的所有项。而在目前公开的方案中,增加写入版本和向读取器释放写入是分开的。通过使用两个全局版本号,读取操作只访问提交完成的项,而不必等待并发操作。

12、在实施例中,写操作仍然可以在它正在写入的项上放置写入锁,以防止其他写操作试图同时写入同一项。在解决写入锁之后,对写入进行版本化。可以通过暂时将项的版本号设置为无效值(在本文中也称为“未指定”值)来实现写入锁,有点类似于已有技术。然而,在当前公开的方案中,读取器可以安全地忽略具有未指定版本号的项,因为它知道它正在使用的读取版本小于一旦释放项将对其进行版本控制的单个版本号。

13、在实施例中,系统可以支持多写原子操作,即在处理器的单个原子操作中执行两个或多个写操作。目前公开的方案还确保,在这种情况下,写操作仍然是可线性化的。回到上面的例子,如果两个写入仅仅是并发的,那么可以以任何顺序线性化它们。如果有两个写入仅仅是并发的(但不是原子的),那么可以以任何顺序线性化它们。仅仅因为对键30的项的写入在90之前开始,并不意味着它将在90之前线性化。如果扫描看到30而没有看到90,那么可以线性化它们,使得30先来。如果它看到90而没有看到30,那么可以先线性化90并且仍然有正确的执行。但是,如果30和90都是同一个多写原子操作的一部分,继而读取器需要看到它们两个或两个都不看到。上述方案也确保了这一点。

14、使用当前公开的方案的一个不利之处是,需要写入器和读取器之间的信令来递增全局读取数,以便释放项。这种信令开销可能不是微不足道的,特别是如果信令是通过例如pcie总线之类的总线。实施例可以通过将更新批处理在一起以进行释放来减轻这种情况。即,写入器不必发送与每个单独的写入器操作相对应的释放信号以每次将全局读取数增加1,而是可以仅每隔几次写入发送释放信号以一次超过一个的跳跃来增加全局读取版本。

15、换句话说,写入器可以被配置为不向读取器发送对应于每个释放指标的释放信号,而是一个接一个地发送至少一些对应于具有不连续版本号的释放指标的释放信号,以便将一些项的释放批处理在一起。

16、多个写入的批量释放减轻了向读取器发送释放信号的信令开销。然而,这并不是必要的,在其他实施例中,可以简单地为每个写入发送一个单独的释放信号,并承受信令开销;或者在某些系统中,特别是如果写入器和读取器紧密连接,那么发送单个释放信号的信令开销可能不会太差。

17、用于批处理释放的一个示例协议如下。这可以应用于写入器包括多个并发线程,并且至少一些写入操作由不同线程执行的情况。在这样的实施例中,系统可以包括一个循环缓冲,该循环缓冲包括n个槽,其索引i=0…n-1。每个写入操作将其释放指示符放置在一个槽中i_r在一个具有n个槽的循环缓冲中,其中i_r=v模n,其中v是相应的版本号。写入器被配置为,当写入操作将相应的释放指示符放置在循环缓冲中时,检查具有紧接着的索引的槽是否包含指示下一个最低版本号的释放指示符,以及

18、如果没有,则将相应的释放指示符留在循环缓冲中,以允许相应的项被另一个写操作释放,但是

19、如果是,则确定一批释放指示符,该批释放指示符包括相应的释放指示符和占据紧接在相应的释放指示符前面的连续槽的任何释放指示符,指示版本号的连续运行,并向读取器发送单个释放信号,该信号指示所确定的运行中的最大版本号,使读取器将全局读取版本更新为该版本号,从而释放与该批释放指示符相对应的一批项。

20、这个特定协议的一个优点是它利用了写入器的非确定性来创建批处理,这比简单地将n个连续项的每次运行批处理在一起更灵活。然而,这只是一个例子,另一种选择确实是将n个项的固定运行批处理在一起,或者每t微秒创建一个批处理,或者像上面提到的那样不使用批处理。

21、在实施例中,数据结构可以包括树结构,例如b+树或其他b树。在一些这样的实施例中,每个叶节点可以包括第一块和第二块,其中第一块中的项按键顺序存储,而新插入的项按时间顺序(即它们被写入的顺序)添加到第二块。第一块可以具有比第二块更大的最大大小,因此第一块可以被称为“大块”,第二块可以被称为“小块”。如果新的写入会导致小块超过其最大大小,则可以将小块合并到大块中。

22、在实施例中,公开的用于版本控制和释放项的方案可以仅应用于第二块中的项。第一块中的项可以替代地与相应节点的单个节点版本相关联。

23、然而,这并不是限制性的,并且在其他实现中,所公开的方案可以应用于节点中的所有项,或者节点的其他细分。此外,所公开的方案不限于在基于树的结构中使用,并且还可以应用于其他形式的数据结构(例如基于表的结构)中的项的写入和读取。

24、在实施例中,写入器可以用软件实现,但读取器可以用定制硬件实现,例如在可编程门阵列(pga)或现场可编程门阵列(fpga)中。

25、提供
技术实现要素:
是为了以简化的形式介绍下面在详细描述中进一步描述的一些概念。发明内容不旨在确定所要求保护的主题的关键特征或基本特征,也不旨在用于限制所要求保护的主题的范围。所要求保护的主题也不限于解决本文中指出的任何或所有缺点的实现。

当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1