huashan8 发表于 2015-7-17 07:10:04

Solr In Action 笔记(4) 之 SolrCloud分布式索引基础

  Solr In Action 笔记(4) 之 SolrCloud Index 基础
  SolrCloud Index流程研究了两天,还是没有完全搞懂,先简单记下基础的知识,过几天再写个深入点的。先补充上前文来不及写的内容。

1. Solr.xml的重要配置
  Solr.xml的内容如下:



1
2   
3   ${host:}
4   ${jetty.port:8983}
5   ${hostContext:solr}
6   ${zkClientTimeout:15000}
7   ${genericCoreNodeNames:true}
8   
9   
11   ${socketTimeout:0}
12   ${connTimeout:0}
13   
14

[*]host , host 指的是Solr节点的IP地址,当Solr节点上线时候,它会向Zookeeper进行注册,注册信息如IP地址就会存储在/clusterstate.json中。这里不但可以直接使用host IP地址如192.168.1.0,也可以使用机器的hostname比如bigdata01。
[*]port , port 指的时Solr用来监听的端口,默认是8983,同样它会存储在/clusterstate.json中。
[*]Solr Host Context, 指的是Solr.war部署的环境路径,多数情况下不用修改。
[*]zookeeper client timeout,上一节讲到过,zookeeper Znode节点变化最大反应时间。
[*]core node name, 该节点控制Solr core的命名策略,如果genericCoreNodeNames为true,那么Solr会给core取普通的名字比如,core_node1 ;如果设为true,则会给core取容易辨别的名字,比如带上host信息,比如10.0.1.7:8983_solr_logmill
[*]Leader Vote Wait Period:
  该参数并未直接在solr.xml中列出来,SolrCloud的leader和其他replica下线只剩最后一个replica的时候,这个Replica并不会立马选举leader,他会等待一段时间,查看leader是否上线,如果上线了,那么leader仍然还是leader,replica仍然还是replica,如果在这个时间段外leader没有上线,那么replica就变为leader了。这个时间就是Leader Vote Wait Period,它的存在防止了当leader和其他replica下线时候,具有旧的数据的node选为leader。
  比如以下一个例子,一个shard有两个node,X为leader,Y为replica,如果X在线,Y下线,那么X仍然可以接受update请求,SolrCloud仍然继续正常运行,只不过leader X不需要再把数据分发给Y了,Y上线后X只需要简单将数据同步给Y就行(Peer sync 策略)。如果X下线,Y在线,那么这个时候因为没有leader接受update请求以及没有leader转发数据,Y是不会接收到update请求的,所以这个时候的SolrCloud的所以建立是无法进行的,所以一旦X挂了SolrCloud就会进行leader选举,但是我们不能立马让Y变为leader,因为Y的数据相比较X来说是旧的数据。如果Y选举为Leader了,那么后续的update他就会接受,过段时间X上线了,由于Y已经是leader了所以X只能是replica,数据的流向变成了Y转发到X,这个时候就发现了奇怪的现象就是X中有部分数据新于Y(Y当选为leader前的数据),Y中有部分数据也新于X(Y当选为leader后的数据),这个时候就需要启动Snapshot replication 策略进行数据复原了,比较麻烦。如果设置了leaderVoteWait 那么X下线后,Y会等待leaderVoteWait时间,这个时间内update操作都是失败的,如果在这时间内X上线了,那么X立马恢复leader状态继续工作,否则就会Y就会变成leader。
  要改善这种情况,可以增加shard和replica的数量,较少leader和replica同时挂掉的可能性。


[*]zkHost,同样没有出现在上面的solr.xml上,它可以在solr.xml的zkHost配置中设置zookeepr集群信息比如192.168.0.1:2181,192.168.0.2:2181表示两个zookeeper组成一个zookeeper集群。

2. SolrCloud的分布式建索引

2.1 Document的Hash
  建好的SolrCloud集群每一个shard都会有一个Hash区间,当Document进行update的时候,SolrCloud就会计算这个Document的Hash值,然后根据该值和shard的hash区间来判断这个document应该发往哪个shard,所以首先让我们先来学习下SolrCloud的hash算法。Solr使用document route组件来进行document的分发。目前Solr有两个DocRouter类的子类CompositeIdRouter(Solr默认采用的)类和ImplicitDocRouter类,当然我们也可以通过继承DocRouter来定制化我们的document route组件。
     之前我们学习过,当Solr Shard建立时候,Solr会给每一个shard分配32bit的hash值的区间,比如SolrCloud有两个shard分别为A,B,那么A的hash值区间就为 80000000-ffffffff ,B的hash值区间为0-7fffffff。默认的CompositeIdRouter hash策略会根据document ID计算出唯一的Hash值,并判断该值在那个shard的hash区间内。
  SolrCloud对于Hash值的获取提出了以下几个要求:


[*]hash计算速度必须快,因为hash计算是分布式建索引的第一步,SolrCloud不可能在这一不上花很多时间。
[*]hash值必须能均匀的分布于每一个shard,如果有一个shard的document数量大于另一个shard,那么在查询的时候前一个shard所花的时间就会大于后一个,SolrCloud的查询是先分后汇总的过程,也就是说最后每一个shard查询完毕才算完毕,所以SolrCloud的查询速度是由最慢的shard的查询速度决定的。我们有理由让SolrCloud做好充分的负载均衡。
  基于以上两点,SolrCloud采用了MurmurHash 算法,那么让我们先来看下该算法的代码,说实话这个代码我真没看懂,等下次独立写个章节学习下MurmurHash算法吧。



1 /** Returns the MurmurHash3_x86_32 hash of the UTF-8 bytes of the String without actually encoding
2    * the string to a temporary buffer.This is more than 2x faster than hashing the result
3    * of String.getBytes().
4    */
5   public static int murmurhash3_x86_32(CharSequence data, int offset, int len, int seed) {
6
7   final int c1 = 0xcc9e2d51;
8   final int c2 = 0x1b873593;
9
10   int h1 = seed;
11
12   int pos = offset;
13   int end = offset + len;
14   int k1 = 0;
15   int k2 = 0;
16   int shift = 0;
17   int bits = 0;
18   int nBytes = 0;   // length in UTF8 bytes
19
20
21   while (pos < end) {
22       int code = data.charAt(pos++);
23       if (code < 0x80) {
24         k2 = code;
25         bits = 8;
26
27         /***
28          // optimized ascii implementation (currently slower!!! code size?)
29          if (shift == 24) {
30          k1 = k1 | (code > 17);// ROTL32(k1,15);
34          k1 *= c2;
35
36          h1 ^= k1;
37          h1 = (h1 >> 19);// ROTL32(h1,13);
38          h1 = h1*5+0xe6546b64;
39
40          shift = 0;
41          nBytes += 4;
42          k1 = 0;
43          } else {
44          k1 |= code > 6))
53             | ((0x80 | (code & 0x3F))0xDFFF || pos>=end) {
57         // we check for pos>=end to encode an unpaired surrogate as 3 bytes.
58         k2 = (0xE0 | (code >> 12))
59             | ((0x80 | ((code >> 6) & 0x3F)) > 12) & 0x3F))) > 6) & 0x3F)))17);// ROTL32(k1,15);
86         k1 *= c2;
87
88         h1 ^= k1;
89         h1 = (h1 >> 19);// ROTL32(h1,13);
90         h1 = h1*5+0xe6546b64;
91
92         shift -= 32;
93         // unfortunately, java won't let you shift 32 bits off, so we need to check for 0
94         if (shift != 0) {
95         k1 = k2 >>> (bits-shift);   // bits used == bits - newshift
96         } else {
97         k1 = 0;
98         }
99         nBytes += 4;
100       }
101
102   } // inner
103
104   // handle tail
105   if (shift > 0) {
106       nBytes += shift >> 3;
107       k1 *= c1;
108       k1 = (k1 >> 17);// ROTL32(k1,15);
109       k1 *= c2;
110       h1 ^= k1;
111   }
112
113   // finalization
114   h1 ^= nBytes;
115
116   // fmix(h1);
117   h1 ^= h1 >>> 16;
118   h1 *= 0x85ebca6b;
119   h1 ^= h1 >>> 13;
120   h1 *= 0xc2b2ae35;
121   h1 ^= h1 >>> 16;
122
123   return h1;
124   }
  最后我们再简单地学习下hash计算的源码吧:


[*]SolrCloud 利用CompositeIdRouter.sliceHash来计算document的hash



1   public int sliceHash(String id, SolrInputDocument doc, SolrParams params, DocCollection collection) {
2   String shardFieldName = getRouteField(collection);
3   if (shardFieldName != null && doc != null) {
4       Object o = doc.getFieldValue(shardFieldName);
5       if (o == null)
6         throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No value for :" + shardFieldName + ". Unable to identify shard");
7       id = o.toString();
8   }
9   if (id.indexOf(SEPARATOR) < 0) {
10       return Hash.murmurhash3_x86_32(id, 0, id.length(), 0);
11   }
12
13   return new KeyParser(id).getHash();
14   }

[*]根据计算出来的hash值计算应该将document发往哪些节点



1 public Collection getSearchSlicesSingle(String shardKey, SolrParams params, DocCollection collection) {
2   if (shardKey == null) {
3       // search across whole collection
4       // TODO: this may need modification in the future when shard splitting could cause an overlap
5       return collection.getActiveSlices();
6   }
7   String id = shardKey;
8
9   if (shardKey.indexOf(SEPARATOR) < 0) {
10       // shardKey is a simple id, so don't do a range
11       return Collections.singletonList(hashToSlice(Hash.murmurhash3_x86_32(id, 0, id.length(), 0), collection));
12   }
13
14   Range completeRange = new KeyParser(id).getRange();
15
16   List targetSlices = new ArrayList(1);
17   for (Slice slice : collection.getActiveSlices()) {
18       Range range = slice.getRange();
19       if (range != null && range.overlaps(completeRange)) {
20         targetSlices.add(slice);
21       }
22   }
23
24   return targetSlices;
25   }

[*]最后,看下SolrCloud是怎么划分shard的hash值区间的。以下代码需要注意几点,

[*]
boolean round = rangeStep >= (1 = (1
页: [1]
查看完整版本: Solr In Action 笔记(4) 之 SolrCloud分布式索引基础