[转载] sensei分布式实时搜寻系统源码解析(二) 分布式Search的流程

[转载] sensei分布式实时搜索系统源码解析(二) 分布式Search的流程

看来自己很懒,发现前同事的sensei 研究了

转载: http://johnnychenjun.blog.163.com/blog/static/1374934062011621111441102/

 

接上节的SenseiServer相关的概念,想必对sensei里面从Server启动到各种server/client的概念有所了解了。本次间隔了一周多来记录一下sensei做分布式search的过程,一周来每次重新跟进去看这些代码每次都有一些新发现和新体会,希望与大家分享,也期望更多的开发者能够了解并加入到分布式实时搜索的讨论中来。
       言归正传
sensei分布式搜索流程
 1.  jetty启动后初始化的SenseiHttpInvokerServiceServlet,接受请求,交由ClusteredSenseiServiceImpl的doQuery方法处理。
 2.  ClusteredSenseiServiceImpl中集成了_clusterClient是获取zookeeper信息的,也就是那些node可以提供查询服务。其中成员变量SenseiBroker具体来执行brower的方法
 3.  senseibroker是一个中间层,通过clusterClient与zookeeper的通信,获取当前提供服务的node信息,并且提供loadbalance的服务,将不同的request根据patition的均衡策略,下发到具体的执行node上
 4.  每个Node上的senseiServer是提供本node服务的,其成员变量里_networkServer是提供本地node查询服务的。_clusterClient与zookeeper来注册或者移除本地node。
_networkServer注册了提供服务的SenseiCoreServiceMessageHandler,coreSenseiService才是实际提供service的类。每个node通过coreSenseiService执行本node的多个patition的查询结果,本地合并。
 5. 各node将查询的结果反馈给senseibroker,在broker这个层级上进行了mergeResult。合并结果反馈给用户显示。
 


[转载] sensei分布式实时搜寻系统源码解析(二) 分布式Search的流程
 
 

       这个图由于工作机无visio就用Edraw画的,没想到导出还有这个trial的标签,凑合着看先。换了张不带trial的图,截图下来的,应该好点。该图上方的虚线以上是我加的,nginx来均衡负载至不同的jettyServer上,提供外部搜索服务的是jettyServer,而在sensei内部的分布式搜索的过程,上述jettyServer, senseibroker,searchNode都是虚拟的,用虚线框起来的部分其实是在一台物理机上。通过norbert框架来实现node的服务提供,broker与node之间的消息序列化和反序列化由protobuf提供。
       上面流程是个概要,里面具体值得展开描述的部分,一个是所谓分布式的请求和负载均衡的实现;另一个是在每个node里多patition搜索的实现。

分布式请求的分发和负载均衡

    流程3当中的分布请求的发送细节的过程其实是这样的:
a) senseiBroker 初始化的时候作为ClusterListener被加入到clusterClient里了,也就是说zookeeper发现有其他node改变的时候,都会通知到senseiBroker,从而其掌握了最新的patition和node的信息。
b)  进行分布搜索的调用的时候,首先通过_loadBalancer.route来对多个patition的分片数据进行路由,对每个patition指定对应的node,然后做个汇集,按照node聚合patition的信息。也就是确定,向哪个node
  的哪几个分区进行搜索的请求。
c) 将req发送至不同的server上,此时的req发送至node时加入了patition的信息。也就是说从broker发送至node上的senseiRequest是带patition信息的。
d) Node上的networkserver接受到请求handlePartitionedRequest,由于req已经包含了partition的信息,AbstractSenseiCoreService在执行execute的时候会将分区信息交给readerFactory,_core.getIndexReaderFactory(partition);来获得
  indexreader来进行查询。
   那么loadbalance的策略是怎样的呢?Sensei提供了两种,默认是UniformPartitionedLoadBalancer,其实是随机的挑node,而RingHashLoadBalancer则是一致性hash的方式来挑,当然也可以自己定制loadbalance的策略。

单个Node的Search流程
   然后再说 流程4中的一个node提供搜索的实现:
a) SenseiServer里的_networkServer接受到senseibroker的请求的时候,交给了coreSenseiService来执行请求。

b) AbstractSenseiCoreService在执行execute方法时,根据请求req中的patition信息来判断是否需要search本地的多个分区,如果查多分区的话,则根据要查的分区数目来进行了多线程的查询调用,该处使用了java concurrent包里面的多线程池来进行_executorService调用。如果只有一个分区就直接执行handleRequest。
c) handleRequest里面来调用handlePartitionedRequest进行每一片索引的查询。
本处值得注意的是在做分区索引查询的时候,首先获取了分区下所有的segmentReaders,采用SenseiIndexPruner来对这些segmentReaders进行裁剪。也就是说根据SenseiRequest中的一些参数来对segment进行选择,减少对segment的查询。不过默认是DefaultSenseiIndexPruner不做任何裁剪,但也可以通过BoboSelectionSenseiIndexPruner裁剪, 即通过只查询包含某个或某几个fact的segment,来减少search的查询时间。 当然还可以通过扩展indexPruner接口来实现适合自己应用的裁剪方案。
d) 裁剪后的validatedReaders来执行bobo的browse查询。
 但这个地方做bobo裁剪本身是否效率高或者说是否值得,我想sensei的工程师也有doubt,所以代码中可以看到一个PruneTimer来记录该耗时。
 
几点细节
1. sensei中需要自己实现 queryBuilder,根据应用需求对从用输入的query转化为lucene中的query,AbstractSenseiCoreService在执行execute方法时将req中的query转为senseiQuery,并执行bobo的browse动作。
 
2. 分布查询请求发送分布问题
    在发送分布式请求的时候,召回的count数的分布,sensei目前是执行的保守策略,前端需要从start获取offset条记录的时候,将向每一个patition发送start+offset条记录的请求。如,需要前5页数据,每页20条。则senseibroker会向每个patition发送从0,开始取100条的req,假设5个patition下来就是会收到500条记录的反馈,然后进行mergeresult,这个地方倒并未做优化。
建议:在我们的实际开发中,则可能通过一些策略或者patition的规则来进行start,offset的粗略计算预测100条的结果分布,这样通过减少需要merge的条目数来降低查询的负载。
 
3. 对于结果的merge有两种,我们上面看到在一个node的多patition涉及合并,在broker上合并来自多个node间的结果,因此也可以看到ResultMerger 的merge函数是带参数boolean onSearchNode,true为在node上的patition的合并,而false时是在broker上的各个node节点的合并。
4. 在sensei的各种执行的过程中,都会有详细的timer来记录执行时间,这点非常nice,如:PruneTimer,GetReaderTimer, SearchTimer,MergeTimer等,这些都是通过了Yammer开源的一个metric ,通过记录下这些状态信息后,通过JMX来进行调用来监控系统的状态。
      总体上说来,sensei的search过程还是集中了很多的高并发,分布式,容错的内容在里面,也使用了多种开源的东东,如zookeeper,protobuf,netty,jetty,nobert,metric,zoie,bobo等。内容还是相当的丰富,对于学习分布式系统而言也很有意义。
      下一部分内容,将对分布索引这块进行解读。