搜索在线服务的存储计算分离
背景 随着网络和存储硬件向着高吞吐低延迟的方向不断发展,存储计算分离成为了集团的一个重要技术方向,在节约成本、简化运维、提高混布能力有着重要的作用:
- 无需或者少量分发索引,使在线服务可以快速迁移,从而提升容灾效率。
- 对于索引数据较大,访问集中的业务,使用存储计算分离后,可以突破单机内存和磁盘空间限制、并释放大量内存。
- 将在线服务的 IO 隔离拆到专有的存储集群,降低了在线服务调度维度。
- 计算节点不再需要考虑存储容量与内存容量的比例。计算节点与存储节点使用不同的服务器硬件,并独立地进行定制和发展。
- 多个节点上的存储资源能够形成单一的存储池,这能降低存储空间碎化、节点间负载不均衡和空间浪费的风险,存储容量和系统吞吐量也能容易地进行水平扩展。
- 数据与计算分离,使在线服务的全图化、算子化更易实现。
文章图片
在管控系统的统一管理下,先是由 BuildService 产出索引到离线 DFS。再经由 DeployExpress 服务,将索引以链式分发的形式复制到所有 Searcher 上,并被 Searcher 加载,以提供服务。搜索的几大引擎,HA3、DII、IGraph,都采用了这种架构。在这个框架下:
- 索引分发一般时间较长,特别是长尾业务,制约了 Searcher 的迁移和扩容效率。
- 索引分发还会产生大量 IO,由于 SSD 写对读的影响较大,而像IGraph、OpenSearch 这些业务都会在查询过程中访问磁盘,写盘会造成这些业务的抖动,为了避免这种情况,需要进行 IO 隔离和限速,这又大大增加了索引分发的时间。
- 为了能够较快的分发索引,一些数据量较大的业务,不得不将索引分成多个 Partition,分发到不同的实例上去。对于表较多,且有一定关联的业务,这会使得他们不得不巧妙设计分列方式,以保证查询性能,使得广播查询不可避免。
- 如果索引很大,还要考虑磁盘与内存的比例,增加运维复杂度。
文章图片
这是在线服务的存储计算分离架构,主目标对象是那种索引很大,冷数据很多,Cache 可以挡住大部分的查询的业务场景,这类业务,普遍存在于 IGraph、OpenSearch、DII。这些业务虽然使用了不同的引擎来实现,受益于统一服务框架,只在底层和管控系统进行了改造就得以实现。下面先介绍一下整体架构设计:
- 索引仍然由 BuildService 产出到离线 DFS,这个 DFS 可以是盘古、也可以是 HDFS,可以使用机械盘,降低成本。
- 相比于分离前,去除了 DeployExpress 链式分发,取而代之的是 Madrox 分布式索引同步服务。
- 本地磁盘也被移除,当然,实际情况还是会有一块盘的,配置、日志、Binary、甚至实时落盘,还是要用的。
- 索引不再分发到在线 Searcher,而是在线的存储集群:盘古。出于稳定性和性能考虑,同时部署了两套盘古,内容一致,互为备份。
- 索引文件以 4KB 为单位划分为若干 Block,查询在需要访问索引时,会先检查 BlockCache 是否存在需求的 Block。仅当 BlockCache 不命中的情况时,才会产生真正的 IO 操作,通过网络读取盘古上的索引文件,并会更新 BlockCache。
盘古集群
文章图片
盘古提供了一种称为 FlatLogFile 的?件接口,对用户呈现顺序写和随机读的语义,类似于本地文件系统的流,不支持随机写。这与我们的全量/增量索引文件用法完全契合,我们的全量/增量索引文件采用的是 Append Only 方式写出,产出后立即封闭,不会再进行任何修改。读取则是以随机方式为主,顺序读取方式为辅。
这使得我们不依赖于块设备,也就不需要使用盘古的 BlockServer,而直接访问 ChunkServer,这样可以节省 BlockServer 到 ChunkServer 的一跳网络延迟开销,以及 BlockServer 的资源开销。
借助 FSLIB 的插件式设计,我们将盘古提供的 Clinet SDK 封装成 FSLIB 的插件,使得在线服务可以直接访问盘古集群。
双盘古集群 这里解释一下双盘古集群设计的考虑。
- 稳定性
- 低延迟需求
- 服务能力
- 副作用:成本问题
未来,我们还会采用 EC(Erasure Coding)的方案来进一步降成本。默认 8+3 的 EC 配置将存储的成本降到约 1.375 份,即双盘古 2.7 份。
Madrox 分布式索引分发服务
文章图片
Madrox 用来解决索引从离线 DFS 到在线盘古的分发问题的。
有两个角色:一个是 API Server,也就是 Master,接受管控系统的分发请求,将索引文件的分发任务派给各个 Worker 也就是 Slave,并收集 Slave 的执行情况,以便反馈给管控系统;另一个是Slave,负责将指定的文件复制到两个盘古之上,并通过心跳汇报进度。
这个服务在设计上重点考虑了以下两个因素:
- 有限且昂贵的机房间长传带宽
- 有限的盘古集群的出带宽
- 一读:保证数据在长传带宽上只出现一次。即 Clinet 只从离线 DFS 上读取一次数据。
- 多写:采用星型写,即从 Client 写到两个盘古的多个 ChunkServer。这样,写出时只会占用盘古的入带宽。
【搜索在线服务的存储计算分离】此外,Madrox 还要考虑一些问题:
- 大文件并发:需要盘古能提供小文件合并大文件,或者 Chunk 级复制的能力。目前,我们只能通过修改索引结构、增量 Segment 等方式实现。
- 仅同步有效数据:因为离线 DFS 索引目录中存在大量的中间及不需要分发的文件。粗暴的目录树同步,会浪费带宽和延长分发时间。
- 双盘古并行写
延迟分析 一次 IO 的延迟开销,主要有这样几个因素:
- 读取磁盘,对于 NVME 的 SSD 来说,平均大约在 100us+,受写影响,大块读影响明显。
- 网络,对于内核态 TCP 协议栈来说,往返平均大约在 120us+,受网络波动、丢包、交换机排队、机架位置等因素影响严重。
- ChunkServer 软件栈、Client 软件栈、线程切换等,平均大约在 50us+,受 IOPS 量影响明显。
为此我们引入了合并读,即一次请求全部所需的 Block,这样可以节省发送请求的网络及磁盘时延开销。
与此同时,我们还通过改进索引查询流程,引入了预取技术,当一个 Block 需要发送 IO 请求时,先分析出其后续的 Block 是否很快被访问到,如果是的,通过合并读,夹带回来,放进 BlockCache 中,这样下次访问时,避免了再次发送 IO 请求。
异步并发读 相较于同步的串行 IO,异步对提升 IOPS,降低延迟的效果是显而易见的。但由于需要对引擎的查询流程做较大变更,还在规划中,很快就会开始实施。
丢包造成的长尾 丢包的直接表现就是长尾的出现,丢包的因素有很多,比如:
- 网络设备的硬件问题:网卡、光模块、网络等不时的抖动
- 交换机连接口的 outdrop:
- 接口有突发流量,导致交换机buffer打满,这种是交换机硬件架构影响。
- 访问流量是多打一,也就是多台机器同时访问一台机器时。
- 万兆的机器访问千兆机器,很容易有outdrop。
- 报文从高速链路进入交换机,由低速链路转发出去;或者报文从相同速率的多个端口同时进入交换机,由一个相同速率的端口转发出去。
- 服务器负载、内核参数不恰当等软件问题
对于丢包,我们只能通过重传来解决,当然,有些时候换一台计算节点更加明智。在分析了 tcpdump 的结果后发现,内核默认的重传间隔是 200ms,这源自 rto_min 参数的默认值 200ms。也就是请求发出后,如果数据包丢失,重发请求已经是 200ms 以后的事了。这时 Query 早已经超时返回了。
通过修改 rto_min=10ms 有效的缓解了客户端请求丢包的情况,将重传时间缩小到 10ms。这里贴一张修改 rto_min 前后的对比图,中间密集的部分,是默认 rto_min=200ms 情况。两边是 rto_min=10ms 的情况。可见长尾延迟有明显的降低。当然代码就是重传率明显上升。
文章图片
也可以看出,rto_min 没能完全解决问题,还有一些情况,比如数据包被网络延迟,甚至重传的包也都延迟超过50ms,rto_min也是无能为力的。重传也会造成网络的进一步恶化。
冷热表分离 在实际的使用当中,我们发现,纯粹的存储计算分离,完全依靠 SearchCache/BlockCache 的方案,存在如下问题:
- Cache 填充慢、预热时间过长,且受 QPS 影响,如果 QPS 较低,可能需要几十分钟。
- 冷启动时,命中率低,延迟过高
- 冷启动时,盘古集群压力过大
- 缺少降级方案,一旦网络或者盘古故障,服务能力完全丧失。
冷热表分离是指,根据提前设定好的规则,在离线构建索引的时候,就将文档分成冷、热两张数据表。热表采用不分离架构,索引分发到本地;冷表则会采用分离架构,在不能命中 SearchCache/BlockCache 的情况下,直接访问盘古的一个折中方案。可以简单的把热表理解成一种静态 Cache,如果规则合理,这个 Cache 可以挡住大部分的访问,极大的降低对存储集群的依赖,从而大幅提高在线服务的性能,并降低延迟。
如果设定规则,是这个方案的难点。
对于主搜索 Summary,由于数据已经做了 Excellent/Good/Bad 的分层,得益于一阶段的查询模式,使用 Excellent 和 Good 做热表,分得能够获得 90%+ 和 98%+ 的命中率。极大的降低了对存储集群的访问压力
对于海神,起初简单的按 KKV 索引的 SKey 链长进行划分,获得了近 80% 的命中率,但热表索引也较大,后来通过与算法团队合作,寻找到了更低索引量情况下,超过 90% 命中率的规则。
冷热分离方案,不仅降低了在线存储集群的访问压力,同时还是一个可靠的降级方案。
分布式Cache 对于行数很多的头部业务,在冷启动时,会对在线存储集群造成较大的冲击,且读取的数据相对集中,由于存储集群规模较小,可能会耗尽存储集群的可用带宽。因此,在计算集群中部署一套分布式Cache,是一个不错的思路。副作用是对于不能命中分布式 Cache 的请求,会增加网络跳数,造成延迟上涨。
展望 搜索在线服务的存储计算分离,还有很长的路要走。特别是需要继续探索,降低存储集群的平均延迟,长尾延迟,提高 IOPS 能力等方面。用好 100G 网络、多核 CPU,引入用户态 TCP、甚至是 RDMA。拓展应用场景,服务好长尾业务。
我们相信,存储计算分离会对搜索架构产生深远的变革性影响。
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量