ZkCoreNodeProps 封装了一个node的相关信息,包括base_url,core_name,state,node_name,core_url,isLeader
SolrCmdDistributor
solr分布式更新的一个重要实现工具类,因为它本身的只负责分布式的请求处理,并没有很多的业务逻辑。
- staticAdjustableSemaphoresemaphore = new AdjustableSemaphore(8);
限制同时并发的请求最多数。从构造函数看可以跟结点数相关,但最大是16.
- public SolrCmdDistributor(intnumHosts, ThreadPoolExecutorexecutor) {
- int maxPermits = Math.max(16, numHosts * 16);
-
- if (maxPermits != semaphore.getMaxPermits()) {
- semaphore.setMaxPermits(maxPermits);
- }
-
- completionService = new ExecutorCompletionService<Request>(executor);
- pending = new HashSet<Future<Request>>();
- }
- privatefinalMap<Node,List<AddRequest>> adds = new HashMap<Node,List<AddRequest>>();
- privatefinalMap<Node,List<DeleteRequest>> deletes = new HashMap<Node,List<DeleteRequest>>();
这两个字段主要是实现用于缓存更新请求
执行缓存的请求,调用下面方法
- publicvoid finish() {
-
-
- flushAdds(1);
- flushDeletes(1);
-
- checkResponses(true);
- }
提交请求
- void submit(UpdateRequestExt ureq, Node node) {
- Request sreq = new Request();
- sreq.node = node;
- sreq.ureq = ureq;
- submit(sreq);
- }
然后是检查响应结果,调用
void checkResponses(booleanblock) 作为检查上一次提交的请求响应。当请求需要重试的时候,会默认重试最大次数10次
将最终结果返回到响应结果里,有异常也会记录下来。
分布式增加更新
- publicvoid distribAdd(AddUpdateCommand cmd, List<Node> nodes, ModifiableSolrParams params) throws IOException {
-
-
- checkResponses(false);
-
-
-
- flushDeletes(1);
-
-
- AddUpdateCommand clone = new AddUpdateCommand(null);
- clone.solrDoc = cmd.solrDoc;
- clone.commitWithin = cmd.commitWithin;
- clone.overwrite = cmd.overwrite;
- clone.setVersion(cmd.getVersion());
- AddRequest addRequest = new AddRequest();
- addRequest.cmd = clone;
- addRequest.params = params;
-
-
- for (Nodenode : nodes) {
- List<AddRequest> alist = adds.get(node);
- if (alist == null) {
- alist = new ArrayList<AddRequest>(2);
- adds.put(node, alist);
- }
- alist.add(addRequest);
- }
-
- flushAdds(maxBufferedAddsPerServer);
- }
其它的doDelete,addCommit的请求逻辑的处理都相差不多
DistributedUpdateProcessor
这个是solrCloud主要的一个更新处理链,使用cloud模式的时候必要的一个处理链,负责分布式更新的逻辑处理
一个重要的hash算法,作为更新记录具体分配到哪个shard的算法
这算法应该会在后期重构并设计为插件方式 ,可被用户自定议的hash算法替换。
- private int hash(AddUpdateCommandcmd) {
- String hashableId = cmd.getHashableId();
-
- return Hash.murmurhash3_x86_32(hashableId, 0, hashableId.length(), 0);
- }
-
- private int hash(DeleteUpdateCommandcmd) {
- return Hash.murmurhash3_x86_32(cmd.getId(), 0, cmd.getId().length(), 0);
- }
其中cmd.getHashableId()方法返回的主要是文档的主键的值
通过hash值定位更新到哪个shard
- private String getShard(int hash, String collection, ClusterState clusterState) {
- return clusterState.getShard(hash, collection);
- }
通过取到collection对应的RangeInfo,计算该hash值座落在哪个Range,就可以计算到相应的shard
- public String getShard(int hash, String collection) {
- RangeInfo rangInfo = getRanges(collection);
-
- int cnt = 0;
- for (Range range : rangInfo.ranges) {
- if (range.includes(hash)) {
- return rangInfo.shardList.get(cnt);
- }
- cnt++;
- }
-
- throw new IllegalStateException("The HashPartitioner failed");
- }
HashPartitioner
做为切分为多个范围的Range,主要实现方法如下:
- public List<Range> partitionRange(int partitions, int min, int max) {
- assert max >= min;
- if (partitions == 0) return Collections.EMPTY_LIST;
- long range = (long)max - (long)min;
- long srange = Math.max(1, range / partitions);
-
- List<Range> ranges = new ArrayList<Range>(partitions);
-
- long start = min;
- long end = start;
-
- while (end < max) {
- end = start + srange;
-
- if (ranges.size() == partitions - 1) {
- end = max;
- }
- ranges.add(new Range((int)start, (int)end));
- start = end + 1L;
- }
-
- return ranges;
- }
指定了某个范围[min,max]切分为多个partitions的Ranges;切分的范围是按平均的切分。
Range类封装了主键hash值范围【min,max】
RangeInfo封装一个collection下所有shard信息对应的Range,主要实现方法如下:
- private RangeInfo addRangeInfo(String collection) {
- List<Range> ranges;
- RangeInfo rangeInfo= new RangeInfo();
- Map<String,Slice> slices = getSlices(collection);
- if (slices == null) {
- throw new SolrException(ErrorCode.BAD_REQUEST, "Can not find collection "
- + collection + " in " + this);
- }
- Set<String> shards = slices.keySet();
- ArrayList<String> shardList = new ArrayList<String>(shards.size());
- shardList.addAll(shards);
- Collections.sort(shardList);
- ranges = hp.partitionRange(shards.size(), Integer.MIN_VALUE, Integer.MAX_VALUE);
- rangeInfo.ranges = ranges;
- rangeInfo.shardList = shardList;
- rangeInfos.put(collection, rangeInfo);
- return rangeInfo;
- }
从上面方法的实现可以看到,会先将所有shard的名称排序,然后根据shard的大小切分相应的多个的范围 ,每一个shard在排序完的位置 有对应的范围Range,两者的信息存放在RangeInfo.
不用担心,上面按整数最小值 ,最大值的平均切分的范围会导致分配不匀的情况,
可能你会担心如果我的主键值是整数,那主键的hash值会不会跟他的值所对应呢,这样的话,会让hash出来的数据先填满小的shard,其它shard不够匀称。其实设计者本身使用的hash算法是针对任何类型,取的主键值也是以字节数组去做hash。这个可以自己使用它的hash算法去校验。
再来看一下DistributedUpdateProcessor
先看add请求,请求的来源有多种:
privateList<Node> setupRequest(inthash)
此方法就是为了判断上面请求来源而决定分发的结点
- 请求来自leader转发:FROMLEADER,那么就只需要写到本地ulog,不需要转发给leader,也不需要转发给其它replicas
- 请求不是来自leader,但自己就是leader,那么就需要将请求写到本地,顺便分发给其他的replicas.
- 请求不是来自leader,但自己又不是leader,也就是该更新请求是最原始的更新请求,那么需要将请求写到本地ulog,顺便转发给leader,再由leader分发
所以为了不让更新请求不会转发来转发去。提交索引的时候,只提交给所有leader是最佳选择。
也就是能预先知道该数据 是要到哪个leader,这个solrj好像有实现。solrcloudserver,分对更新的数据预先做分发请求。
先来讲一下增加的更新逻辑
- @Override
- public void processAdd(AddUpdateCommand cmd) throws IOException {
- int hash = 0;
- if (zkEnabled) {
- zkCheck();
- hash = hash(cmd);
-
- nodes = setupRequest(hash);
- } else {
- isLeader = getNonZkLeaderAssumption(req);
- }
-
- boolean dropCmd = false;
- if (!forwardToLeader) {
- dropCmd = versionAdd(cmd);
-
- }
-
-
- if (dropCmd) {
-
- return;
-
- ModifiableSolrParams params = null;
- if (nodes != null) {
-
- params = new ModifiableSolrParams(filterParams(req.getParams()));
- }
-
- params.set(DISTRIB_UPDATE_PARAM,
- (isLeader ?
- DistribPhase.FROMLEADER.toString() :
- DistribPhase.TOLEADER.toString()));
- if (isLeader) {
- params.set("distrib.from", ZkCoreNodeProps.getCoreUrl(
- zkController.getBaseUrl(), req.getCore().getName()));
- }
-
- params.set("distrib.from", ZkCoreNodeProps.getCoreUrl(
- zkController.getBaseUrl(), req.getCore().getName()));
-
-
- cmdDistrib.distribAdd(cmd, nodes, params);
- }
-
- if (returnVersions && rsp != null && idField != null) {
- if (addsResponse == null) {
- addsResponse = new NamedList<String>();
- rsp.add("adds",addsResponse);
- }
- if (scratch == null) scratch = new CharsRef();
- idField.getType().indexedToReadable(cmd.getIndexedId(), scratch);
- addsResponse.add(scratch.toString(), cmd.getVersion());
- }
- }