沪江搜索平台化之路

279 阅读7分钟
原文链接: mp.weixin.qq.com

作者:曹林华

本文为原创文章,转载请注明作者及出处

背景

随着沪江业务的高速发展以及数据爆炸式的增长,当前公司各产线都有关于搜索方面的需求,但是目前的搜索服务系统由于架构与业务上的设计,不能很好的满足各个业务线的期望,主要体现下面三个问题:

  1. 不能支持对语句级别的搜索,大量业务相关的属性根本无法实现

  2. 没有任何搜索相关的指标评价体系

  3. 扩展性与维护性特别差

基于现状,对行业内的搜索服务做出充分调研,确认使用 ElasticSearch 做底层索引存储,同时重新设计现有搜索服务,使其满足业务方对维护性、定制化搜索排序方面的需求。

整体技术架构

沪江搜索服务底层基于分布式搜索引擎 ElasticSearch,ElasticSearch 是一个基于 Lucene 构建的开源、分布式、RESTful 搜索引擎;能够达到近实时搜索、稳定、可靠、快速响应的要求。

搜索服务整体分为 5 个子系统:

  • 搜索服务(Search Server) : 提供搜索与查询的功能

  • 更新服务(Index Server):提供增量更新与全量更新的功能

  • Admin 控制台:提供 UI 界面,方便索引相关的维护操作

  • ElasticSearch 存储系统: 底层索引数据存储服务

  • 监控平台:提供基于 ELK 日志与 zabbix 的监控

外部系统接口设计

  • 查询接口

    • 查询接口提供 HTTP 的调用方式,当出现跨机房访问的时候,请使用 HTTP 接口,其余都可以使用 Dubbo RPC 调用

  • 增量更新接口

    • 数据增量更新接口采用提供 MQ 的方式接入。当业务方出现数据更新的时候,只需将数据推送到对应的 MQ 通道中即可。更新服务会监听每个业务方通道,及时将数据更新到 ElasticSearch 中

  • 全量索引接口

    • 更新服务会调用业务方提供的全量 HTTP 接口(该接口需提供分页查询等功能)

全量更新

众所周知,全量更新的功能在搜索服务中是必不可少的一环。它主要能解决以下三个问题:

  • 业务方本身系统的故障,出现大量数据的丢失

  • 业务高速发展产生增减字段或者修改分词算法等相关的需求

  • 业务冷启动会有一次性导入大批量数据的需求

基于上面提到的问题,我们与业务方合作实现了全量索引。但是在这个过程中,我们也发现一个通用的问题。在进行全量更新的时候,其实增量更新也在同时进行,如果这两种更新同时在进行的话,就会有遇到少量增量更新的数据丢失。比如说下面这个场景:

  1. 业务方发现自己搜索业务 index_1 数据大量数据丢失,所以进行索引重建。其中 index_1 是别名,就是我们通常说 alias,但是底层真正的索引是 index_201701011200 (建议:索引里面包含时间属性,这样就能知道是什么创建的)。

  2. 首先创建一个新的索引 index_201706011200,然后从数据中拉出数据并插入 ES 中,并记录时间戳 T1,最后索引完成的时间戳为T2,并切换搜索别名 index_1 指向 index_201706011200。

  3. 索引创建成功之后的最新数据为 T1 这个时刻的,但是 T1 到 T2 这段时间的数据,并没有获取出来。同时 index_201701011200 老索引还在继续消费 MQ 中的数据,包括 T1 到 T2 时间内的缺少数据。

  4. 所以每次索引重建的时候,都会缺少 T1 到 T2 时间内的数据。

最后,针对上面这个场景,我们提出通过 zookeeper 分布式锁来暂停 index consumer 的消费,具体步骤如下

  1. 创建 new_index

  2. 获取该 index 对应的别名,来修改分布式锁的状态为 stop

  3. index consumer 监控 stop 状态,暂停索引数据的更新

  4. new_index 索引数据创建完毕,更新分布式锁状态为 start

  5. index consumer 监控 start 状态,继续索引数据的更新

这样的话,我们就不用担心在创建索引的这段时间内,数据会有缺少的问题。相信大家对于这种方式解决全量与增量更新数据有所体会。

集群无缝扩容

数据量爆炸式的增加,导致我们 ES 集群最终还是遇到了容量不足的问题。在此背景下,同时结合 ES 本身提供的无缝扩容功能,我们最终决定对线上 ES 集群进行了在线的无缝扩容,将从原来的 3 台机器扩容为 5 台,具体步骤如下:

  • 扩容前准备

    • 目前我们线上已经有 3 台机器正在运行着,其中 node1 为 master 节点,node2 和 node3 为 data 节点,节点通信采用单播的形式而非广播的方式。

    • 准备 2 台(node4 与 node5)机器,其中机器本身配置与 ES 配置参数需保持一致

  • 扩容中增加节点

    • 启动 node4 与 node5 (注意一个一个启动),启动完成之后,查看node1,2,3,4,5 节点状态,正常情况下 node1,2,3 节点都已发现 node4 与 node5,并且各节点之间状态应该是一致的

  • 重启 master node

    • 修改 node1,2,3 节点配置与 node4,5 保持一致,然后顺序重启 node2 与 node3,一定要优先重启 data node,最后我们在重启 node1(master node)。到此为止,我们的线上 ES 集群就在线无缝的扩容完毕

部署优化

  • 查询与更新服务分离

    • 查询服务与更新服务在部署上进行物理隔离,这样可以隔离更新服务的不稳定对查询服务的影响

  • 预留一半内存

    • ES 底层存储引擎是基于 Lucene,Lucenede 的倒排索引是先在内存中生成,然后定期以段的形式异步刷新到磁盘上,同时操作系统也会把这些段文件缓存起来,以便更快的访问。所以 Lucene 的性能取决于和 OS 的交互,如果你把所有的内存都分配给 Elasticsearch,不留一点给Lucene,那你的全文检索性能会很差的。所有官方建议,预留一半以上内存给 Lucene 使用

  • 内存不要超过 32G

    • 跨 32G 的时候,会出现一些现象使得内存使用率还不如低于 32G,具体原因请参考官方提供的这篇文章 Don’t Cross 32 GB!(地址:https://www.elastic.co/guide/en/elasticsearch/guide/current/heap-sizing.html#compressed_oops)

  • 尽量避免使用 wildcard 

    • 其实使用 wildcard 查询,有点类似于在数据库中使用左右通配符查询。(如: \*foo\*z 这样的形式)

  • 设置合理的刷新时间

    • ES 中默认 index.refresh_interval 参数为 1s。对于大多数搜索场景来说,数据生效时间不需要这么及时,所以大家可以根据自己业务的容忍程度来调整

总结

本章主要介绍沪江搜索服务的整体架构,重点对全量更新中数据一致性的问题,ES 在线扩容做了一定的阐述,同时列举了一些沪江在部署 ES 上做的一些优化。本文的主要目的,是希望通过沪江搜索实践,能够给广大读者带来一些关于搭建一套通用搜索的建议。另外,关于搜索服务中排序以及打分的问题,后续会在「沪江技术学院」中发布。

End

推荐阅读

统一错误码管理平台建设思路

翻译首发!「Stack Overflow 的 HTTPS 化:漫漫长路的终点」

专访「Android 首席医生」徐宜生:善于利用工具来解决问题的程序员才是好的工程师

沪江Web前端技术团队倾情翻译「前端开发者指南 (2017)」震撼来袭!