本文从架构的角度介绍了有赞搜索系统的演进背景以及希望解决的问题。有赞搜索平台是针对公司内部各种搜索应用和部分NoSQL存储应用的PaaS产品,有助于应用合理高效的检索和多维过滤功能。有赞搜索平台目前支持大大小小100多种搜索服务,服务近百亿数据。有赞搜索平台在为传统搜索应用提供高级检索和大数据交互能力的同时,还需要为商品管理、订单检索、粉丝筛选等其他海量数据过滤提供支持。从工程的角度来看,如何扩展平台以支持多样化的检索需求是一个巨大的挑战。我们的搜索团队目前主要负责平台的性能、扩展性和可靠性,并尽可能降低平台的运维成本和业务开发成本。Elasticsearch是一个高可用的分布式搜索引擎。一方面技术相对成熟稳定,另一方面社区也相对活跃。因此,我们在搭建搜索系统的过程中,也选择了Elasticsearch作为我们的基础引擎。架构1.0回到2015年,当时在生产环境运行的有赞搜索系统是由若干个高端虚拟机组成的Elasticsearch集群,主要运行产品和粉丝索引,数据通过Canal从DB同步到Elasticsearch。总体架构如下图所示:这种方式在业务量较小时,可以低成本快速创建不同业务指标的同步应用,适用于业务快速发展时期。但是每个同步程序都是一个单独的应用,不仅和业务库的地址耦合,还需要适应业务库的快速变化,比如数据库迁移、分库分表等,多个Canals同时订阅同一个库。导致数据库性能下降。此外,Elasticsearch集群并不是物理隔离的。在一次宣传活动中,Elasticsearch进程的Heap内存因为巨大的fan数据量被耗尽而OOM,导致集群中的所有索引都无法正常工作。这给我上了深刻的一课。.架构2.0在解决上述问题的过程中,我们也很自然地沉淀了有赞搜索的2.0架构。总体架构如下:首先,数据总线同步数据变化消息到MQ,同步应用通过消费MQ消息同步业务,数据库数据可以通过数据总线与业务数据库解耦,数据的引入bus也可以避免多个Canals监听消耗同一张表Binlog的浪费。高级搜索(AdvancedSearch)随着业务的发展,我们逐渐出现了一些比较集中的流量入口,比如分发和选择。这时候普通的Bool查询就不能满足我们对搜索结果进行细粒度速率排序控制的需求了。把复杂的function_score等专业的高级查询编写和优化任务交给业务开发,显然不可取。我们这里考虑的是通过一个高级查询中间件拦截业务查询请求,解析出必要的条件后重新组装成高级查询,交给引擎执行。大体结构如下:这里的另一个优化是增加了搜索结果缓存,常规的文本检索查询Match每次执行时都需要实时计算。在实际应用场景中,这不是必须的。用户在一定时间内(比如15、30分钟)通过同一个请求访问同一个搜索结果是完全可以接受的。在中间件中做一个结果缓存,可以避免重复查询重复执行的浪费,同时提高中间件的响应速度。大数据集成搜索应用离不开大数据。除了通过日志分析从用户行为中挖掘更多价值,离线计算和排序综合评分也是优化搜索应用体验不可或缺的一环。在2.0阶段,我们通过开源的ES-Hadoop组件搭建了Hive和Elasticsearch的交互通道。大体结构如下:通过Flume收集搜索日志,存储到HDFS中供后续分析,或者通过Hive分析后导出为搜索提示。当然,大数据为搜索业务提供的功能远不止于此,这里仅举几个功能。遇到的问题这样的架构支撑搜索系统运行了一年多,但也暴露出不少问题,首先就是维护成本越来越高。除了Elasticsearch集群维护和索引本身的配置和字段变更,虽然数据总线已经和业务库解耦了,但是耦合在同步程序中的业务代码仍然给团队带来了巨大的维护负担。消息队列虽然在一定程度上降低了我们与业务的耦合度,但是由此带来的消息顺序问题,对于不熟悉业务数据状态的我们来说也是难以处理的。另外,流经Elasticsearch集群的业务流量对我们来说是一种半黑盒状态,可以感知但不可预测。因此,曾经出现过在线集群被内部大流量错误调用压到CPU爆满无法服务Fault的情况。当前的架构3.0解决了2.0时代的问题。我们在3.0的架构中做了一些有针对性的调整,罗列要点:通过开放接口接收用户调用,与业务代码完全解耦。为对外服务添加Proxy,对用户请求进行预处理并进行必要的流控、缓存等操作。提供管理平台,简化索引变更和集群管理。这种演进使得有赞的搜索体系逐渐平台化,搜索平台架构初具规模。Proxy作为对外服务的入口和出口,不仅通过ESLoader提供了兼容不同版本Elasticsearch调用的标准化接口,还内嵌了请求校验、缓存、模板查询等功能模块。请求校验主要是对用户的写请求和查询请求进行预处理。如果发现字段不匹配、类型错误、查询语法错误、疑似慢查询等,将以FastFail或较低流控率的形式拒绝请求。水平执行,避免无效或低效操作对整个Elasticsearch集群的影响。Cache和ESLoader主要是整合了原有高级搜索中的通用功能,让高级搜索可以专注于查询本身的查询分析和重写排序功能,更加内聚。我们在缓存上做了一点优化,因为查询结果缓存通常和源文档有比较大的内容,为了避免流量高峰时频繁访问导致Codis集群网络拥塞,我们实现了一个简单的本地on代理缓存,它会在流量高峰期间自动降级。这里提到模板查询。当查询结构(DSL)比较固定和冗长时,如商品类目筛选、订单筛选等,可以通过模板查询(SearchTemplate)来实现。一方面简化了业务编排DSL的负担,另一方面也可以编辑查询模板Template,在服务端使用默认值、可选条件等进行在线查询性能调优。管理平台为了减少日常索引增删改、字段修改、配置同步等维护成本,我们实现了基于Django的搜索管理平台初版。主要提供了一套索引变更的审批流程和将索引配置同步到不同集群的功能,实现索引元数据的可视化管理,减少我们日常维护平台的时间成本。由于开源的Head插件在效果展示上的不友好,以及一些粗制滥造的功能的暴露:如上图,可以点击字段进行索引排序,并按照指定的方式展示结果场地。在早期版本中,Elasticsearch会通过Fielddata加载需要排序的字段内容。如果字段数据量比较大,很容易导致Heap内存变满,导致FullGC甚至OOM。为了避免此类问题的反复出现,我们还提供了定制化的可视化查询组件,以支持用户浏览数据的需求。因为ES-Hadoop只能通过控制Map-Reduce的数量来调整读写流量,ES-Hadoop实际上是根据Elasticsearch是否拒绝请求来调整自己的行为,这对线上集群是相当不友好的。为了解决这种不可控的离线读写流量,我们在现有DataX的基础上开发了ESWriter插件,可以实现记录条数或者流量大小的秒级控制。面临挑战平台化和配套文档体系降低了用户的准入门槛。随着业务的快速增长,Elasticsearch集群本身的运维成本也逐渐让我们难以承受。虽然有多个集群是物理隔离的,但不可避免的是多个业务指标会共享同一个物理集群,这对不同业务之间不同的生产标准没有很好的支持,在同一个集群中部署了太多的指标也是给生产环境的稳定运行带来隐患。另外,集群服务能力的弹性伸缩相对困难。节点横向扩展需要机器申请、环境初始化、软件安装等步骤。如果是实体机,则需要较长的机器采购流程,无法及时响应服务能力的不足。未来架构4.0当前架构通过开放接口接受用户的数据同步需求。虽然实现了与业务的解耦,降低了我们自己的开发成本,但是相对的用户开发成本也变高了。从数据库到索引,数据需要经过三个步骤:从数据总线获取数据,同步应用处理数据,调用搜索平台开放接口写入数据。其中,从数据总线获取数据和写入搜索平台这两个步骤会在多个业务的同步程序中重复开发,造成资源浪费。这里我们目前也在准备与PaaS团队自研的DTS(DataTransporter,DataSynchronizationService)集成,通过配置实现Elasticsearch与各种数据源的数据自动同步。为解决共享集群应对不同生产标准应用的问题,我们希望将平台化搜索服务进一步升级为云化服务应用机制,协同业务层级划分,独立部署核心应用,相互隔离物理集群。非核心应用通过不同的应用模板申请运行在K8S上的Elasticsearch云服务。应用模板定义了不同应用场景下的服务配置,解决了不同应用生产标准差异的问题,云服务可以根据应用运行情况适时扩展服务。
