solrCloud 索引更新逻辑学习笔记

来源:互联网 发布:淘宝空间怎么登陆 编辑:程序博客网 时间:2024/05/16 06:19

ZkCoreNodeProps 封装了一个node的相关信息,包括base_url,core_name,state,node_name,core_url,isLeader

SolrCmdDistributor

solr分布式更新的一个重要实现工具类,因为它本身的只负责分布式的请求处理,并没有很多的业务逻辑。

[java] view plain copy
  1. staticAdjustableSemaphoresemaphore = new AdjustableSemaphore(8);  

限制同时并发的请求最多数。从构造函数看可以跟结点数相关,但最大是16.

[java] view plain copy
  1. public SolrCmdDistributor(intnumHosts, ThreadPoolExecutorexecutor) {  
  2.     int maxPermits = Math.max(16, numHosts * 16);  
  3.     // limits how many tasks can actually execute at once  
  4.     if (maxPermits != semaphore.getMaxPermits()) {  
  5.       semaphore.setMaxPermits(maxPermits);  
  6.     }  
  7.   
  8.     completionService = new ExecutorCompletionService<Request>(executor);  
  9.     pending = new HashSet<Future<Request>>();  
  10.   }  


[java] view plain copy
  1. privatefinalMap<Node,List<AddRequest>> adds = new HashMap<Node,List<AddRequest>>();  
  2. privatefinalMap<Node,List<DeleteRequest>> deletes = new HashMap<Node,List<DeleteRequest>>();  


这两个字段主要是实现用于缓存更新请求

执行缓存的请求,调用下面方法

[java] view plain copy
  1. publicvoid finish() {  
  2.   
  3.    // piggyback on any outstanding adds or deletes if possible.  
  4.    flushAdds(1);  
  5.    flushDeletes(1);  
  6.   
  7.    checkResponses(true);  
  8.  }  


提交请求

[java] view plain copy
  1. void submit(UpdateRequestExt ureq, Node node) {  
  2.     Request sreq = new Request();  
  3.     sreq.node = node;  
  4.     sreq.ureq = ureq;  
  5.     submit(sreq);  
  6.   }  

然后是检查响应结果,调用

void checkResponses(booleanblock)  作为检查上一次提交的请求响应。当请求需要重试的时候,会默认重试最大次数10次 


将最终结果返回到响应结果里,有异常也会记录下来。

分布式增加更新

[java] view plain copy
  1. publicvoid distribAdd(AddUpdateCommand cmd, List<Node> nodes, ModifiableSolrParams params) throws IOException {  
  2.   
  3. //执行前都会去掉之前还会检查可能没响应完的请求,不等待,直接删除旧的请求。  
  4.     checkResponses(false);  
  5.       
  6.     // 确保所有删除的请求被执行  
  7.   
  8.     flushDeletes(1);  
  9.      
  10.       //克隆更新请求重用  
  11.     AddUpdateCommand clone = new AddUpdateCommand(null);  
  12.     clone.solrDoc = cmd.solrDoc;  
  13.     clone.commitWithin = cmd.commitWithin;  
  14.     clone.overwrite = cmd.overwrite;  
  15.     clone.setVersion(cmd.getVersion());  
  16.     AddRequest addRequest = new AddRequest();  
  17.     addRequest.cmd = clone;  
  18.     addRequest.params = params;  
  19.   
  20.        //增加对每个节点的请求到缓存adds里  
  21.     for (Nodenode : nodes) {  
  22.       List<AddRequest> alist = adds.get(node);  
  23.       if (alist == null) {  
  24.         alist = new ArrayList<AddRequest>(2);  
  25.         adds.put(node, alist);  
  26.       }  
  27.       alist.add(addRequest);  
  28.     }  
  29.     //执行缓存adds的请求  
  30.     flushAdds(maxBufferedAddsPerServer);  
  31.   }  

其它的doDelete,addCommit的请求逻辑的处理都相差不多


DistributedUpdateProcessor


这个是solrCloud主要的一个更新处理链,使用cloud模式的时候必要的一个处理链,负责分布式更新的逻辑处理

一个重要的hash算法,作为更新记录具体分配到哪个shard的算法

这算法应该会在后期重构并设计为插件方式 ,可被用户自定议的hash算法替换。

[java] view plain copy
  1. private int hash(AddUpdateCommandcmd) {  
  2.     String hashableId = cmd.getHashableId();  
  3.       
  4.     return Hash.murmurhash3_x86_32(hashableId, 0, hashableId.length(), 0);  
  5.   }  
  6.     
  7.   private int hash(DeleteUpdateCommandcmd) {  
  8.     return Hash.murmurhash3_x86_32(cmd.getId(), 0, cmd.getId().length(), 0);  
  9.   }  

其中cmd.getHashableId()方法返回的主要是文档的主键的值

通过hash值定位更新到哪个shard

[java] view plain copy
  1. private String getShard(int hash, String collection, ClusterState clusterState) {  
  2.     return clusterState.getShard(hash, collection);  
  3.   }  


 通过取到collection对应的RangeInfo,计算该hash值座落在哪个Range,就可以计算到相应的shard

 

[java] view plain copy
  1. public String getShard(int hash, String collection) {  
  2.    RangeInfo rangInfo = getRanges(collection);  
  3.     
  4.    int cnt = 0;  
  5.    for (Range range : rangInfo.ranges) {  
  6.      if (range.includes(hash)) {  
  7.        return rangInfo.shardList.get(cnt);  
  8.      }  
  9.      cnt++;  
  10.    }  
  11.     
  12.    throw new IllegalStateException("The HashPartitioner failed");  
  13.  }  


HashPartitioner


做为切分为多个范围的Range,主要实现方法如下:


[java] view plain copy
  1. public List<Range> partitionRange(int partitions, int min, int max) {  
  2.     assert max >= min;  
  3.     if (partitions == 0return Collections.EMPTY_LIST;  
  4.     long range = (long)max - (long)min;  
  5.     long srange = Math.max(1, range / partitions);  
  6.   
  7.     List<Range> ranges = new ArrayList<Range>(partitions);  
  8.   
  9.     long start = min;  
  10.     long end = start;  
  11.   
  12.     while (end < max) {  
  13.       end = start + srange;  
  14.       // make last range always end exactly on MAX_VALUE  
  15.       if (ranges.size() == partitions - 1) {  
  16.         end = max;  
  17.       }  
  18.       ranges.add(new Range((int)start, (int)end));  
  19.       start = end + 1L;  
  20.     }  
  21.   
  22.     return ranges;  
  23.   }  

指定了某个范围[min,max]切分为多个partitions的Ranges;切分的范围是按平均的切分。

Range类封装了主键hash值范围【min,max】

RangeInfo封装一个collection下所有shard信息对应的Range,主要实现方法如下:

[java] view plain copy
  1. private RangeInfo addRangeInfo(String collection) {  
  2.     List<Range> ranges;  
  3.     RangeInfo rangeInfo= new RangeInfo();  
  4.     Map<String,Slice> slices = getSlices(collection);  
  5.     if (slices == null) {  
  6.       throw new SolrException(ErrorCode.BAD_REQUEST, "Can not find collection "  
  7.           + collection + " in " + this);  
  8.     }  
  9.     Set<String> shards = slices.keySet();  
  10.     ArrayList<String> shardList = new ArrayList<String>(shards.size());  
  11.     shardList.addAll(shards);  
  12.     Collections.sort(shardList);    
  13.     ranges = hp.partitionRange(shards.size(), Integer.MIN_VALUE, Integer.MAX_VALUE);  
  14.     rangeInfo.ranges = ranges;  
  15.     rangeInfo.shardList = shardList;  
  16.     rangeInfos.put(collection, rangeInfo);  
  17.     return rangeInfo;  
  18.   }  

从上面方法的实现可以看到,会先将所有shard的名称排序,然后根据shard的大小切分相应的多个的范围 ,每一个shard在排序完的位置 有对应的范围Range,两者的信息存放在RangeInfo.

不用担心,上面按整数最小值 ,最大值的平均切分的范围会导致分配不匀的情况,

可能你会担心如果我的主键值是整数,那主键的hash值会不会跟他的值所对应呢,这样的话,会让hash出来的数据先填满小的shard,其它shard不够匀称。其实设计者本身使用的hash算法是针对任何类型,取的主键值也是以字节数组去做hash。这个可以自己使用它的hash算法去校验。


再来看一下DistributedUpdateProcessor



先看add请求,请求的来源有多种:


privateList<Node> setupRequest(inthash) 

此方法就是为了判断上面请求来源而决定分发的结点

  1. 请求来自leader转发:FROMLEADER,那么就只需要写到本地ulog,不需要转发给leader,也不需要转发给其它replicas
  2. 请求不是来自leader,但自己就是leader,那么就需要将请求写到本地,顺便分发给其他的replicas.
  3. 请求不是来自leader,但自己又不是leader,也就是该更新请求是最原始的更新请求,那么需要将请求写到本地ulog,顺便转发给leader,再由leader分发


所以为了不让更新请求不会转发来转发去。提交索引的时候,只提交给所有leader是最佳选择。

也就是能预先知道该数据 是要到哪个leader,这个solrj好像有实现。solrcloudserver,分对更新的数据预先做分发请求。

先来讲一下增加的更新逻辑

[java] view plain copy
  1. @Override  
  2.   public void processAdd(AddUpdateCommand cmd) throws IOException {  
  3.     int hash = 0;  
  4.     if (zkEnabled) {//cloud模式下  
  5.       zkCheck();//检查zk连接状态  
  6.       hash = hash(cmd);//取得更新请求hash值,再决定hash到哪一个shard  
  7.       //判断更新请求来源,决定需要转发的nodes  
  8.       nodes = setupRequest(hash);  
  9.     } else {  
  10.       isLeader = getNonZkLeaderAssumption(req);  
  11.     }  
  12.       
  13.     boolean dropCmd = false;  
  14.     if (!forwardToLeader) {//不需要转发,即是请求来源是由leader转发来的,或者本人就是leader  
  15.       dropCmd = versionAdd(cmd);//决定该请求是否丢弃,丢弃原因:1)只需要更新到ulog,也就是请求是来源leader转发来的,不需写到索引。2)ulog已有该更新记录且版本比当前的版本更新,则可以丢弃。  
  16.   
  17.     }  
  18.   
  19.    // 可以丢弃该请求,不需要写到lucene索引  
  20.     if (dropCmd) {  
  21.       // TODO: do we need to add anything to the response?  
  22.       return;  
  23.       
  24.     ModifiableSolrParams params = null;  
  25.     if (nodes != null) {  
  26.         
  27.       params = new ModifiableSolrParams(filterParams(req.getParams()));  
  28.       }  
  29.        //需要转发该请求,转发leader,或者由leader分发  
  30.       params.set(DISTRIB_UPDATE_PARAM,   
  31.                  (isLeader ?   
  32.                   DistribPhase.FROMLEADER.toString() :   
  33.                   DistribPhase.TOLEADER.toString()));  
  34.       if (isLeader) {  
  35.         params.set("distrib.from", ZkCoreNodeProps.getCoreUrl(  
  36.             zkController.getBaseUrl(), req.getCore().getName()));  
  37.       }  
  38.   
  39.       params.set("distrib.from", ZkCoreNodeProps.getCoreUrl(  
  40.           zkController.getBaseUrl(), req.getCore().getName()));  
  41.   
  42.       //转发请求到nodes      
  43.       cmdDistrib.distribAdd(cmd, nodes, params);  
  44.     }  
  45.      //增加更新的响应内容   
  46.     if (returnVersions && rsp != null && idField != null) {  
  47.       if (addsResponse == null) {  
  48.         addsResponse = new NamedList<String>();  
  49.         rsp.add("adds",addsResponse);  
  50.       }  
  51.       if (scratch == null) scratch = new CharsRef();  
  52.       idField.getType().indexedToReadable(cmd.getIndexedId(), scratch);  
  53.       addsResponse.add(scratch.toString(), cmd.getVersion());  
  54.     }  
  55.   }  
原创粉丝点击