我们在多年的HA3开发工作当中,形成了一套高效的数据存储和管理方案,将其推广到其它业务场景(包括iGraph、DII服务的场景)后也取得了不错的效果。这篇文章首先介绍在典型的搜索场景,我们的在线数据存储和管理框架是如何发挥作用的,然后描述将这个框架推广到更多的业务场景的过程以及取得的收益。
1 搜索场景的特点
一般的搜索场景满足以下几个特点:
- 数据量大。doc数目达到几个Billion,总索引存储空间量超过几十TB是常见的事情。
* 访问量大。一个大型的搜索业务日pv上数十亿是很正常的压力。
* 数据更新量大。以主搜双11为例,excellent集群每秒的正排更新qps可达几十万每秒,如此大的更新量给在线服务引擎带来了很大的压力。
* 数据更新的时效性很高,需要到秒级以内。例如主搜场景,对某个宝贝的大量的用户点击行为应该能够实时影响到结果的排序,在我们的引擎中会转变为大量的正排更新。
2 在线数据存储和管理框架
经过了HA3多年的积累,我们形成了一套高效的在线数据存储和管理框架(以下简称”框架”),它的原理如下图所示。
2.1 “框架”的基本工作原理
图中,离线部分负责索引的产出,在线部分负责响应用户的query。
在离线部分,Build Service负责索引的构建和优化。它由三个角色组成:processor、builder和merger,每一个角色都可以多实例、分布式的运行。Processor可以处理来自dfs(比如hdfs或pangu)的原始doc,处理逻辑可能包括分词、doc内容规范化以及其它业务处理逻辑,然后将处理后的数据(processed doc)写入到消息队列服务swift的relay topic中。此外,processor也可以处理来自swift input topic中的数据。Builder的职责是处理来自relay topic中的processed doc,生成索引并保存到dfs上。Merger的职责是对builder产出的索引进行整理和优化,以获得更少的存储空间和更高的访问性能。
在线部分由多行多列的searcher组成矩阵式架构,例如下图中是由三行四列组成,其含义是将索引分成4个部分,并将每一列复制三份。其中每一个searcher节点接受并响应对所加载数据的查询请求,某一行的四台searcher可以完成对整个数据集的检索,而多列的结果合并是由result merger完成的。Searcher加载的索引数据是在离线build service产出索引后远程分发到本地磁盘上的。Searcher节点还有一个重要的功能,它会实时的拉取来自swift relay topic中的 processed doc,并在内存中build实时索引以及提供对其的检索。下图描述了builder service、swift和searcher节点的数据流向以及searcher节点的内部工作过程。Searcher实时build的效果是doc一旦进入searcher的内存,就形成了对它的索引结构,searcher可以立即提供对该doc的搜索,这是一种真正的实时搜索,而不是像Solr/Lucene的near realtime依赖于segment的dump频率。
2.2. 索引过程
在我们的“框架”中,按照产出过程,索引可分为三类:
- 全量索引。全量索引本质上解决的是一个大数据集的索引从无到有的问题。以主搜为例,每天dump中心会在dfs上提供一份全量源数据,build service会对其进行处理生成全量索引。全量索引由于涉及到的数据量很大,生成过程涉及到的I/O和计算量巨大,build service通过支持processor、builder和merger都能够充分的并行来缩短全量索引的产出时间。
- 实时索引。离线系统会通过swift 的input topic写入实时数据,build service的processor会对其实时地处理,将处理后的processed doc写入到relay topic中。每个Searcher节点会有一个build线程,实时地从relay topic中拉取processed doc,并利用一大块内存中build索引。我们将这个大块内存称为building segment,一篇doc一旦进入这个大块内存的索引building segment,便能够被搜索到。当building segment大小达到一定程度时,会触发整理形成一个stable segment。随着doc的不断进入,stable segment会越来越多,Building segment和所有的stable segment组成实时索引。如果stable segment过多,实时索引部分的检索性能会变得很差,这就引入一个实时索引的优化问题。
- 增量索引。增量索引本质上解决的是索引的优化问题。增量的builder会定期启动,处理relay topic中的实时processed doc并生成增量索引,merger会对其进行整理和优化。优化后的增量索引分发到searcher节点的磁盘上后,新到来的增量索引和当时的实时索引会有数据重叠部分,我们需要做的是将实时索引中的重叠部分回收掉,本质上这个过程整理和优化了实时索引。下图描述了增量和实时数据相互重叠的情况,其中A和C部分是重叠的,我们需要把A回收掉,整个增量索引的打开和实时索引的回收过程应以尽可能的不影响实时build为前提。
build service支持增量merge过程充分并行,这样可以保证增量merge时间充分短。目前build service还不支持单列的build并行,这是我们下一步计划支持的。
2.3. 数据模型
为了解决好各种搜索场景的需求,我们利用indexlib提供了如下数据模型:
- 普通表
被广泛用到淘宝主搜、open search、b2b搜索以及神马搜索等业务场景,每篇doc可以拥有多个正排、多个倒排以及多组summary。多年以来,在正排、倒排以及summary等方面我们做了很多的性能优化或存储空间优化,具体请参照相关资料部分。 - 主子表
在天猫搜索中使用,每篇doc由一篇主doc和多篇子doc组成,每篇主(子)doc都可以拥有多个正排、多个倒排,每篇主doc可以拥有多组summary。
ha3通过对普通表和主子表的高效访问,完成搜索场景的各种需求。
3 对典型搜索场景问题的解决
实际的运行结果表明,“框架”很好的解决了典型的搜索场景的问题,我们也可以从原理上做如下分析。
- 通过将数据划分成多列,解决了数据量大单机难以管理的问题;
- 通过多行,解决了访问量过高导致单行服务能力不够的问题。
- 通过build service不断优化索引,保证了随着数据的不断更新单个节点的性能不会明显降低。
- build service的各个角色可以充分并行,使得索引产出和优化的时间足够短。
- 多列分摊了更新压力;
- 实时索引机制解决了查询的时效性需求。
4 “框架”的特点
前面提到,我们通过build service、swift消息队列以及在线searcher节点组成的矩阵式架构提供了在线数据存储和处理框架,这个框架是面向最终一致性的。这里的最终一致性是指,每一列的不同行增量索引分发完成的时间点不同,加载的时间点也不同,实时索引的build进度也不相同,这样同一时间同一query查询不同行可能会得到不同的结果,但随着索引的不断分发和实时索引的不断build,可以保证最终的结果能够一致。这个框架具有如下特点:
- 快速的索引优化。Processor、builder和merger都可以做到充分并行,离线的计算资源和存储资源得以充分利用,繁重的索引build和整理工作都能够在很短的时间内完成。
- 快速的数据恢复。某个节点当掉时,只需要选择一个新的节点,分发最新的增量索引,并从最新的增量索引时间点拉取实时数据。如果没有build service定期做增量索引的过程,新节点的启动要以全量索引为基础拉取实时数据build实时索引,如果很久没有做全量,这个拉取过程会很长。
- 快速扩大服务能力。当线上访问压力过大需要扩行时,同样要启动新的searcher节点,分发最新的增量索引,并从这个增量版本的时间点开始拉取实时数据。
5 “框架”的进一步推广
5.1 iGraph和DII
iGraph是一个功能强大的在线图存储和计算服务,被广泛的用到集团的各推荐业务以及主搜个性化等场景,它提供了kv表和kkv表等数据模型,用户一般将图计算中涉及到的“点”用kv模型存储,“边”用kkv模型存储。
DII是一个支持灵活定制的链式处理的在线计算引擎,在搜索业务的qp、suggest以及推荐场景的猜你喜欢等有深度的应用,它提供了kv模型,并使用了indexlib提供的普通表模型。
下图描述了iGraph和DII原有的架构。
此架构中有如下特点:
- iGraph和DII都有全量索引和实时索引过程。全量索引由build job产生,实时索引由在线的服务节点产生。
- 实时索引的性能有逐渐恶化的现象,这时要依赖于在线compaction,但是这会引擎在线节点的I/O和CPU争用,造成查询抖动。
- 节点迁移的时间很长。需要迁移时,需要分发全量索引,并以全量时间点为基础拉取实时数据,如果很久没有build全量索引,那么这个拉取实时数据build实时索引的时间会非常长。
5.2 统一在线数据存储和管理框架
针对iGraph和DII碰到的问题,我们结合以往的经验,提出了统一的在线数据存储和管理框架,并将iGraph和DII迁移到这个框架上,如下图所示。
在这个框架中:
- 在indexlib中新增加了kv表、kkv表和Trie表模型,再加上我们已经支持的普通表(倒排表)、主子表模型,这个框架提供了十分丰富的数据模型,各引擎可以根据各取所需。
- 通过kiwi封装了对indexlib各种表模型的查询逻辑,包括表达式计算(Expression)、统一结果表达方式(MatchDoc)、查询执行逻辑(QueryExecutor)和过滤逻辑(Filter)等。kiwi使得各引擎接入indexlib的多种模型十分轻松。
- Build service负责全量和增量索引的产出,并对实时doc进行部分地处理。在线节点负责实时索引的build以及响应用户查询。在线节点实时索引保证了查询服务的时效性,增量索引保证了索引不断被优化,查询性能不会明显退化。
- 在线服务节点迁移的时间大大缩短。在之前的系统中,迁移节点需要从去全量索引拉取实时,而在新的框架下,只需要从最新的增量索引开始拉取。
- 避免了在线compaction,降低了CPU和I/O争用。虽然增量索引分发过来时也有I/O,但触发的I/O量要至少小一半。
- 我们为iGraph和DII提供了更丰富的数据模型,例如为iGraph增加了倒排召回的能力,为DII增加了kkv表召回的能力。由于有了更大的能力,使得它们可以满足更多的需求,比如DII在“猜你喜欢” offline 转online项目中,就在召回阶段大量使用了kkv模型。
- 这个框架不是为iGraph和DII专用的,它对于面向最终一致性的在线数据存储和计算服务来说都是适用的。
6 展望
当单个节点的数据量很大时,索引分发的时间仍然很长。很多大数据量应用的qps压力并不高,对本地磁盘的压力也不会太大。对于这样特点的应用来说,我们计划采用存储计算分离的思想将在线数据存储在dfs上,在线节点使用cache对热数据进行缓存,本地磁盘上只存储实时索引(也可能在本地存储一些高频数据),这样可以进一步提高节点迁移或恢复的速度,同时也会降低存储成本。