背景
微医搜索系统是以接口的形式提供服务的。任何业务方需要搜索的服务,搜索会从各个数据源获取数据,同步进solr或者es这种数据载体之中。虽然es和solr都有各自的api,但是对用户来说并不友好,并且无法做一些业务逻辑。因此,搜索会在这些api外层继续封装一层接口提供服务。
所以,对外暴露的接口就是用户的访问数据源的渠道。它的响应速度直接影响着用户的服务。
优化方向
搜索对响应时间的优化可以从两个方面来看,一个是硬件,一个是软件。
硬件
硬件方面可以从两方面优化,一个是增加服务器的数量,一个是提高服务器的性能。
增加服务器数量
增加服务器的数量,搜索可以进行一个业务隔离。这就好比数据库,多个数据库实例布置在一套集群上,互相之间是可以互相影响的。增加服务器最大的好处,是可以把重要的服务隔离,减少互相之间的影响,提高服务的稳定性,从而降低接口响应时间。
增加服务器还有另外一种操作,可以做成双机房的形式。把业务流量分散到2个不同的机房,降低单集群的压力,以此达到降低接口响应时间的目的。
提升服务器性能
搜索系统是一个很占用内存的系统。
同时,为了保证数据的一致性,搜索系统有recovery这个保护操作(简单的说,就是如果集群间数据版本差异较大,服务器之间会停止服务,重新同步数据)。因此,recovery操作的直接影响就是,暂停了recovery的服务器对外提供服务的能力。recovery是一个同步数据的过程,所以服务器的带宽影响着recovery的时间。如果服务器之间的传输速度提高了,那么出现recovery问题的,同步时间也会减少,减少对服务的影响时间。
最后,搜索更多的数据是存在磁盘上的,搜索系统不仅需要和缓存交换数据,更需要和磁盘有大量的交互。提升磁盘的io同样是硬件的优化方向之一。
综上所述,在降低服务起响应时间方面,服务器可以从增加缓存,更换固态硬盘,增加高速率带宽的网卡和交换机三个方面进行优化。
软件
从软件方面要优化搜索的接口首先需要了解搜索的流程。搜索从数据流程方面可以分为建索引和查询2个阶段。所以在软件方面,可以分成架构,索引和查询三个方面考虑优化。
架构
搜索的查询是涉及磁盘的io和内存的使用。服务在构建大批量索引的时候,搜索系统的大部分资源被用来处理集群数据了,还可能会recovery。这个时候如果提供服务,响应时间会提升。所以,从架构方面需要优化的有2方面,一方面隔离索引和查询的服务器,减少索引服务器查询和构建索引的互相影响。另一方面,可以错开建索引和查询的时间。把大批量更新索引数据的服务器放在查询量少的时间段执行,比如凌晨的时间。从而降低建索引对查询时间的影响。
索引
索引对查询的影响主要是在两个方面。单个索引的大小,还有索引的频率。如果短时间内业务方更新了大批量的数据更新,而搜索只构建了一个文档。意味着提交的文档非常大,文档数非常多,那么服务器接受的压力也很大。搜索的实时监控,是监控到一条数据,就更新一条数据,这样一来,服务器要不停的刷新索引,重复的构建索引,导致数据更新变慢,查询速度同样变慢。这种情况下,我们需要问业务方的需求,能否把一些数据的服务减少到批量更新。
所以,在索引段的优化,一个方面是减少单个索引的大小,一方面是降低索引的频率。从这两方面优化索引。
查询
查询部分是最关键也是最直接的优化内容。我们会从查询的参数,缓存,拆分shard,路由机制,外部依赖五个方面进行优化。
查询的参数优化
搜索查询的过程中,主要是fq的优化。fq的查询原理是先把每个fq的文档从luence中过滤出来,然后把所有的fq过滤的文档去重组合起来返回用户。组合的过程非常耗时。由于用户查询的条件是比较固定的,这时候可以把fq的条件组合到一起进行查询。在luence底层就做这么一个查询,而不需要组合查询。做过一个实验,把fq条件组合到一起查询能够把响应时间医生37倍,效果非常明显。
缓存的优化
搜索查询的过程中,搜索系统是自带缓存策略的。由于数据的更新,搜索需要不断的更新缓存。每次更新的缓存如果存在期很短,那么命中率也不会高,对待新的查询很难有好的表现。为了应对这些问题,搜索系统对于缓存有着一套预热的机制。在每次缓存需要重新更新的时候,可以从原有的缓存中获取一定的查询缓存进行预热。这样就不需要每次都去重新更新了。这样的方式,可以在fq优化后的基础上进一步优化3倍。但是也会带来一个问题,实时数据的更新性能会下降。预热的时间会延缓新数据的展示时间。
因此,基于搜索系统的缓存优化适合实时性低的接口优化使用。
拆分shard
一个索引十分大的时候,查询数据的量级也是很大的。solr针对每个collection,有一个shard机制。也就是一个业务的collection,你可以把它进行分片,相当于一个大的collection拆分成了小的块儿进行查询,从而提高响应速度。比如一个查询大部分请求有一个固定查询,性别查询。我们就可以把性别作为一个区分shard的标准,分成2个块儿。这样,每次请求进来,只需要查询固定的shard的即可。这种效果提升非常明显。
另外,如果没有明显的区分条件,分shard的效果具有两面性。一方面,由于要从各个shard获取数据并且合并,所以平均查询时间会变长。但是,接口的稳定性却提高了不少。99%的索引查询完成时间降低了。这说明,在提高平均响应时间的同时,它把那些“查询毛刺”也给降低了。
分shard还有一个好处,可以降低但块儿数据的索引量。那么当发生recovery的时候,它可以降低那么当发生recovery的时候的时间。
路由机制
在solr中,每个collection是分布在不同的服务器中的。但是不论哪个服务器,都能够响应服务的,但是中间会涉及一个转发的过程。这时候,如果我们知道每个节点的状态,可以直接把请求直接发送到leader节点或者拥有分片的服务器,从而降低服务的响应时间。
外部依赖
如果一些索引不仅查询的条件一致,连查询的值也是一致的。我们可以在外部,通过已有的中间件(codis,redis等)进行外部缓存处理,如果进来的url匹配上了,直接返回而不走搜索系统,从而降低响应时间。但是这种方式要充分考虑使用场景,否则,会极度加大中间件的压力。
总结
综上所述,我们从软件,硬件2个大方面展开,讲了搜索接口的主要优化方向和方法,其中着重介绍了查询端的五个方法。通过上述方法的不断优化,大部分搜索接口对比最开始的响应超时率能够提升百倍甚至上千倍。目前,我们的核心接口3s超时率都在十万分之一以下。