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、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网 享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com