本小节主要讨论hazelcast如何使数据在各个节点被共享,以及如何做到自动负载均衡
以最常用的Distributed Map为例子, 任意一个节点put的key和value在同集群中的其他节点中都能get到.
首先在Node A 上
import
com.hazelcast.core.Hazelcast;
import
java.util.Map;
import
java.util.Collection;
Map<String, String> mapCustomers = Hazelcast.getMap(
"customers"
);
mapCustomers.put(
"1"
,
"Customer1"
));
mapCustomers.put(
"2"
,
"Customer2"
);
mapCustomers.put(
"3"
,
"Customer3"
);
Collection<String> colCustomers = mapCustomers.values();
for
(String customer : colCustomers) {
System.out.println(customer);
}
|
Node A上的输出
Customer1
Customer2
Customer3
|
接着在Node B上
import
com.hazelcast.core.Hazelcast;
import
java.util.Map;
import
java.util.Collection;
Map<String, String> mapCustomers = Hazelcast.getMap(
"customers"
);
Collection<String> colCustomers = mapCustomers.values();
for
(String customer : colCustomers) {
System.out.println(customer);
}
|
Node B上的输出
Customer1
Customer2
Customer3
|
可以看到在Node B可以查询到Node A上所插入的数据.下面就来看hazelcast是如何实现的,先来看几个比较主要的类
com.hazelcast.impl.Block
com.hazelcast.impl.CMap
com.hazelcast.impl.Record
|
Block[271] : 这份信息在整个集群中都是一致的.HazelCast把集群分成271个Block,并且把这些Block平均分配给集群中的节点分别管理.
Block: ID 表示在block[271]数组中的index, ownerAddress 记录这个Block被哪个节点管理, migrateAddress 记录Block会被转移到哪个节点管理.
CMap: 每个节点各自维护一份CMap, mapRecords 这个ConcurrentMap 用来存放 要put到hazelcastmap里的Key和Value. 这个mapRecords 的key是序列化之后的KeyData, value是Record.
Record: 主要包含BlockID, KeyData, ValueData. 其中BlockID 根据 KeyData的hash % 271 计算得出.
put(k,v)
假设在Node A上执行 put(k,v), Node A的hazelcast instance 首先根据key k的hash%271 得出Block id以及这个Block的owner(假设是Node B). 然后Node A序列化key和value的信息发给B, B根据keyData和valueData生成新的Record,插入到mapRecords中去. 同时A也会把这个信息发送给之后的1~3个节点作为备份.
get(k)
根据put的方法我们可以知道, 同样算出key的存放的block, 通过block查出owner, 从owner上的mapRecords返回Record以及ValueData, 反序列化.
节点增加时如何数据负载均衡
增加节点时的负载均衡分为2步
1.假设原来有A B两个节点, 那么A会负责136个block,B会负责135个block. 当c加入 A,B,C 需要分别管理91,90,90. 这时有2种情况,A的mapRecords里面只使用了不到90个BlockID,也就是说至少有45个BlockID没有被使用. 那么Hazelcast就会把这45个block的ownerAddress设为C, 同理B也会分出45个Block给C. 这种是最理想状态,结束时A,B,C分别管理91,90,90. 由于从A,B中分出的BlockID都没有别使用过,所以不存在之前的key因为换了个Owner而找不到value的情况. 最差的情况自然是 135个全被使用了. 那么A,B,C保持136,135,0. 这个过程被称之为quickBlockRearrangement.
2.同时Master(虽然hazelcast是p2p,但是在内部还是设置最早的加入的节点作为master,以方便处理splitbrain以及下面的reArrangeBlocks)会起一个线程每个10ms做一次reArrangeBlocks.
这个reArrangeBlocks会做下列步骤,
1. 计算每个node当前应该管理的block数,如果超过则把block放入lsBlocksToRedistribute这个list中. 这里A会把多余的136-90 = 46 个block 放到lsBlocksToRedistribute中.
2. 如果当前node管理数少于平均block数, 移出lsBlocksToRedistribute中的Block, 加入到当前node, 并设置block的migrateAddress为当前node.
3. 把新的Blocks[]设置同步到集群所有节点,并触发startMigration
4. startMigration 会将之前的Block owner中的cmap record移出掉并在migrateAddress node上生成新的record.当然也需要重新做backup
5. 重新设置这个block的owneraddress 为 migrateAddress,将 migrateAddress 设置为空.
节点减少是如何数据负载均衡
Master上的ClusterManager发出的HeartBeater如果发现有一个Member失去conenction或者dead.会往集群中其他节点发出removeDeadMember的信息.接着把owner 是这个dead member的block owner 设置为下一个活着的member也就是之前backup过的1~3个.然后在所有node上做下面两个操作
1. 查看自己的cmap里record的block id是不是属于之前remove member的,如果是 设置为dirty.
2. 查找到previous或者next member 发生变化的member,遍历他的record, 如果这个record的block的owner是自己,那么就重新做一次backup到之后的1~3个节点.
同样如果有一个节点的分到的block大于平均值,master线程会做reArrangeBlocks.
以上简要的描述了下hazelcast的数据共享以及负载平衡机制,具体实现起来较为繁琐有许多要注意的细节.有兴趣的同学们可以自己去参看下源码.接下来几章会参考代码介绍HazelCast的网络通信机制,以及一个新Node如何Join到一个Cluster中