搜索系统架构现状
搜索架构简介
搜索系统概述
微医智能搜索引擎系统通过将搜索技术、用户画像技术、推荐系统、关键词分析、海量数据查询分析等多种技术相结合,并结合全链路的监控系统、日志系统和HA技术,致力于为用户提供最智能、最精准、最丰富、最稳定的个性化检索体验,使医疗健康数据的价值最大化。
微医的搜索系统是一个综合的数据服务系统,不仅提供了通用含义上的搜索和推荐的接口服务,还承担了很多数据服务的功能。虽然用户直接的感知度不高,但搜索的服务在整个业务流转过程中扮演了重要的角色。
2019年7月份,基层卫生服务依赖的搜索系统所在的机房网络出现问题,导致双机房间网络通信中断,搜索的很多索引无法从主机房向另一个机房同步,并且造成了集群中很多节点状态异常,导致搜索Solr集群异常,无法提供服务。这次问题造成依赖搜索服务的基卫云相关系统不可用超过半小时,对使用基卫云系统的医生、政府机关人员和相应的用户都造成了很大的影响。因此搜索系统的健壮性直接关系到核心业务系统的稳定性和公司核心业务的正常进行。
搜索系统架构简介
搜索系统可以分成三个部分,索引部分,存储部分和接口部分。针对每个业务,搜索系统会生成一个独立的collection(业务数据集),相当于数据的一张大宽表进行存储。多个业务的collection构成了一个系统。每个业务一般都会有独立的索引脚本,存储的业务collection,还有独立的接口服务。下面从各个组成部分简单介绍一下。
索引部分
索引部分会从各个数据源(数据库,hbase,hive,kafka等)获取数据,组装成业务需要的数据结构(大宽表),生成存储系统(solr)能够识别的数据格式(现阶段采用xml的文件格式),发送(http的post方式)给solr。
存储部分
存储部分的核心是一个多台服务器构成的集群,使用solrcloud的方式构建一个基于solr的搜索引擎集群。solr集群在初始化阶段会预先定义好一个数据格式(solr中的schema),类似于数据的大宽表的表结构,通过zookeeper上传这个结构,然后solr集群通过zookeeper载入最新的配置进行业务collection初始化。最后这个业务的collection,会通过api的形式接受索引端传过来的数据,存储到solr集群中去。此时solr提供了api的方式进行查询。
接口部分
上一小节介绍了solr可以自身提供api进行查询,但是solr的api语法对外界并不友好,而且针对一些特殊的逻辑查询,solr无法直接处理。所以对外的服务,搜索系统需要重新封装一次。使用tornado这种技术,提供更有好的交互给业务方。
现有架构存在的问题
上述搜索系统结构如图一所示
中间的云状集群就是一个集群的结构,也就是系统中的存储部分。集群的机器分布在2个机房,滨安机房2台,兴义机房3台。接口服务也是独立部署了2套对外服务,滨安的服务和兴义的服务,分别布置在2个机房。整个集群依赖于公司内部的zookeeper。在索引部分中,分为增量和实时的索引。在增量索引中,只有兴义的一台机器会构建索引文件,发向集群。实时的消息会被兴义和滨安的2台机器进行消费。一份索引文件提交到集群一次即可。在这里,搜索用了缓存队列的方式,防止2台机器重复消费的问题。
从结构上讲,虽然索引服务和接口服务都是独立于每个机房的,但是最终访问的还是整个集群。整个集群的依赖有很多,这些外部问题会导致服务的不可用。另外集群内部针对每个业务的collection也有稳定性的不足。针对这两方面,现在的主要有2部分问题。
外部因素
在分析外部因素中,先从集群的结构入手。集群一共依赖的外部条件有以下几个:
- 网络问题
- zookeeper
- kafka消息
- 数据库
网络一旦出现问题,比如兴义服务断开,那么集群是无法使用的。当zookeeper出现问题时,集群就失去了同步的能力,同样会导致集群不可用。一旦集群不可用,那么对外的整个接口服务也不能提供服务。
另外,索引部分依赖于kafka消息和数据库,如果kafka消息停止发送,那么实时将会停止。这个影响范围有限,因为针对每个业务都有增量索引,是覆盖掉实时的,也就是数据更新有秒级别变成了至多10分钟的更新。如果数据库停止服务了,那么所有的数据更新都会停止。搜索系统的实时和增量大多数都是从数据库获取的,因此数据的服务稳定性会直接影响数据更新的及时性。但是,索引部分即使出现问题,接口服务和存储部分都是正常的,服务还是会正常运行,只是数据停止了更新。
内部因素
分析了外部因素,现在从集群的角度分析下内部的因素。集群内部最重要的就是每个业务的稳定性。每个业务在集群的内部表现形式是单独的collection。一旦某个collection出现了问题,那么这个collection相关业务就会出现问题。
业务collection有2种情况会导致出问题,一种是人为因素,一种是外部因素。
每个业务的业务collection都有自己的配置,就像数据库的每个字段可以配置很多的功能。solr和数据库不一样的地方在于它的独立性不是很强,业务collection多一个配置和少一个配置有可能会破坏现有业务collection的结构,导致了整个collection的索引数据错误。一旦数据被破坏,那么整个业务也就不可查询使用了。
业务的colleciton在大批量更新数据的时候,是一个建立索引的过程,这个过程如果持续时间长,那么这段时间内的响应速度也会表慢。如果在这个阶段,大量的请求进来,会导致整个业务的collecion的响应超时,直到整个collecition无法响应为止。这就是外部因素导致的业务的colleciton不可用。
解决办法
前面几个小节详细介绍了搜索的结构和搜索目前存在的问题。总结上述问题,可以分为外部因素和内部因素两个方面原因。现在就从这两个因素针对的解决。
外部因素解决方法
外部因素主要有四个方面,网络问题,zookeeper问题,kafka消费问题,mysql问题。
针对这四个问题,设计了下列解决方案,具体如图二所示。
集群设置成独立的两套集群,这样就不存在网络不互通造成的问题。如果兴义机房出现网络问题,那么滨安机房是正常运行的。这个时候把流量全部切到滨安机房即可恢复。如果是图一的架构,任何一个机房出现问题,都会导致服务不可用。现有的集群利用的是公司现有的zookeeper,搜索集群依赖于zookeeper。所以当zookeeper不可用时,机房同样不可用。所以在图二中对每个集群做了zookeeper备份。备份的zookeeper和使用的zookeeper保持一样的配置文件格式,方便随时切换。kafka消费问题和mysql问题都属于数据源问题,相较于前两个问题,这个只影响数据的更新,不影响服务的使用。这两个数据源,公司中已经有了两个集群独立的地址,但两个地址的关系并不是一起使用,而是互为主备。因此,搜索系统一般只使用兴义机房的配置。现在需要在独立的滨安机房搭建一套滨安的数据环境,配合滨安的kafka消息和mysql数据源。
滨安机房和兴义机房虽然是独立运行的2套集群,但是对外提供的数据服务是一致的,所以两套集群需要保证数据的一致性,这样才能提供更加优质的业务服务。试想,如果你看到的数据和你同事在一个网站上或者app上看到的数据不一致,相差比较大,那么毫无疑问,会对你的服务使用产生很大的干扰。
搜索系统的数据更新原理是获取原数据,根据原数据构建索引文件(solr能够识别的文件格式,这里采用xml文件格式),然后把文件发送给solr进行数据更新。所以在双集群的操作中,搜索系统会生成一份索引文件,发向两个集群。保证数据的一致性。
但是这种方案存在一个问题。solr的每次发布过程中,一般需要reload对应的collection,就像数据库中修改表结构一样。修改表结构,就意味着可能会有不可知的问题出现。为了保证数据的正确性,在发布阶段,一般按照顺序发布集群。这里假设先发布滨安,再发布兴义机房。一旦兴义机房出现问题,可以把流量切换到滨安集群。因为滨安集群是还是稳定的老代码。但是这里存在一个问题,在发布的过程中,兴义和滨安的集群的collection配置并不一样。这就意味着,在兴义机房上生成的数据文件发送到滨安机房是不可用的。那么这个时候,这段时间内的数据更新服务,对于滨安机房来说,是停止的。所以搜索系统提出了图三的架构设计。
在发布阶段,采用图三的索引构建方式。之前文中提到过,搜索的滨安机房会备份好数据更新的独立脚本,但是不启用。这是在发布阶段以外时间不启用。在发布阶段,滨安集群可以启用这个脚本。相当于发布阶段,两边的集群独自建索引,自己更新自己的。兴义机房的collection是新的,它的索引脚本也是新的。滨安机房同理,collection和索引脚本都是老版本。那么兴义机房和滨安机房的数据都是同步更新的。也许你会提问,既然两边独立建索引,而且collection也不一样,那么数据是不是也不一样呢?对于这个问题,需要从搜索的接口属性来解释。每个系统的更新迭代,都是要保证原有数据的可用性基础上进行升级的。也就是新的功能不能影响原有的功能。对于搜素的接口来说,只能新增字段而不能修改字段或者删除字段。因此两边系统虽然数据结构不同,但是原有数据服务是一致的。你也许会问,那不是搜索接口又增加了一些字段吗?针对这个问题,我们需要了解多业务方的协作方法。一般一个业务代码要上线,各业务部门是一个从下到上的过程。就是最底层的数据开始发布,一直到所有数据准备全了,再发布最终的业务版本。所以搜索的数据服务一般发布于业务系统之前,因此新的业务并不会使用。在搜索上线后用的是老的业务,所以双机房在发布阶段分开建索引是符合数据规范的。
内部因素解决方法
上述小节介绍了解决外部依赖产生的问题的方案,内部的因素也值得讨论。内部的因素即人为造成的业务bug。搜索的集群强壮型不够,它的底层业务同样不是很健壮。修改一些字段的属性或者业务压力的增加,都可能导致collection的索引的不可用。这个时候,依赖于这个collection的业务可能就无法使用了。在这里,搜索提出了图四的架构解决这个问题。
对每一个业务collection做一个备份,相当于一个业务有2各结构一模一样的表。互为主备,任何一个坏了,可以切换到另一个。对于整个索引来说,这是空间换安全的一种方式。如果仅仅用来备份,以防万一,那么资源利用率并不高。所以对于备份的collection,搜索系统也做了一定的用途。针对整个业务,搜索的响应时间有快有慢。因此,根据业务的重要程度,搜索切分了流量。把需要快速响应的流量切换到主的collection,把比较慢的查询(如统计查询,分页爬取逻辑查询)切换到备份的colleciton。通过这种方式,做到了数据流量的分流操作,也做到了colleciton的备份方案。
总结
本文简单介绍了搜索系统主要的能力和作用,架构现状和存在的问题。从系统架构层面分析了当前系统出现问题的原因,提出了双机房高可用的方案,从内外因素两个方面解决了当前系统架构存在的问题,提高了系统的整体稳定性和健壮性,从而为微医各个业务的用户提供更加智能、更加稳定的搜索和数据服务。