- 基于权重实现的投票验证器:QuorumHierarchical
上篇文章解析了基于机器数量实现的投票验证器,基于权重的有一个优点,就是可以让部署在性能更好的机器、网络更好的机房等能提供更稳定、更优质的zookeeper服务拥用更大的投票权. - 官方给出的一份权重配置:
group.1=1:2:3
group.2=4:5:6
group.3=7:8:9
weight.1=1
weight.2=1
weight.3=1
weight.4=1
weight.5=1
weight.6=1
weight.7=1
weight.8=1
weight.9=1
解析如下:
1.划分为3个group,每个group的value都是serverId,以":"分隔
2.每个serverId的权重值都是1
配置格式如下:
group.groupId = serverId1:serverId2
weight.serverId = weight值
- 构造函数中解析权重配置:
public QuorumHierarchical(Properties qp) throws ConfigException {
parse(qp);
LOG.info(serverWeight.size() + ", " + serverGroup.size() + ", " + groupWeight.size());
}
private void parse(Properties quorumProp) throws ConfigException{
for (Entry<Object, Object> entry : quorumProp.entrySet()) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
if (key.startsWith("server.")) {
int dot = key.indexOf('.');
long sid = Long.parseLong(key.substring(dot + 1));
QuorumServer qs = new QuorumServer(sid, value);
allMembers.put(Long.valueOf(sid), qs);
if (qs.type == LearnerType.PARTICIPANT)
participatingMembers.put(Long.valueOf(sid), qs);
else {
observingMembers.put(Long.valueOf(sid), qs);
}
} else if (key.startsWith("group")) {
int dot = key.indexOf('.');
long gid = Long.parseLong(key.substring(dot + 1));
numGroups++;
String parts[] = value.split(":");
for(String s : parts){
long sid = Long.parseLong(s);
if(serverGroup.containsKey(sid))
throw new ConfigException("Server " + sid + "is in multiple groups");
else
serverGroup.put(sid, gid);
}
} else if(key.startsWith("weight")) {
int dot = key.indexOf('.');
long sid = Long.parseLong(key.substring(dot + 1));
serverWeight.put(sid, Long.parseLong(value));
} else if (key.equals("version")){
version = Long.parseLong(value, 16);
}
}
for (QuorumServer qs: allMembers.values()){
Long id = qs.id;
if (qs.type == LearnerType.PARTICIPANT){
if (!serverGroup.containsKey(id))
throw new ConfigException("Server " + id + "is not in a group");
if (!serverWeight.containsKey(id))
serverWeight.put(id, (long) 1);
}
}
computeGroupWeight();
}
private void computeGroupWeight(){
for (Entry<Long, Long> entry : serverGroup.entrySet()) {
Long sid = entry.getKey();
Long gid = entry.getValue();
if(!groupWeight.containsKey(gid))
groupWeight.put(gid, serverWeight.get(sid));
else {
long totalWeight = serverWeight.get(sid) + groupWeight.get(gid);
groupWeight.put(gid, totalWeight);
}
}
/*
* Do not consider groups with weight zero
*/
for(long weight: groupWeight.values()){
LOG.debug("Group weight: " + weight);
if(weight == ((long) 0)){
numGroups--;
LOG.debug("One zero-weight group: " + 1 + ", " + numGroups);
}
}
}
解析如下:
1.循环解析quorumProp对象的键值对
2.如果以"server."开头,则说明在配置集群服务器,接下来则会解析serverId,并创建QuorumServer,然后根据服务器类型放入相应的map中
3.如果以"group"开头,则说明在配置服务器组,接下来解析groupId和serverId,将group计数器numGroups加一,并放入相应的map中
4.如果以"weight"开头,则说明在配置服务器权重,解析出serverId和权重值,然后放入相应的map
5.如果以"version"开头,则解析出版本值并缓存
6.赋予未配置权重的服务器权重为默认值1
7.计算服务器组的权重,如果服务器组的权重为0,那么将不会计入参与投票的组,将计数器numGroups减一
判断投票是否通过
public boolean containsQuorum(Set<Long> set){
HashMap<Long, Long> expansion = new HashMap<Long, Long>();
/*
* Adds up weights per group
*/
if(set.size() == 0) return false;
else LOG.debug("Set size: " + set.size());
for(long sid : set){
Long gid = serverGroup.get(sid);
if (gid == null) continue;
if(!expansion.containsKey(gid))
expansion.put(gid, serverWeight.get(sid));
else {
long totalWeight = serverWeight.get(sid) + expansion.get(gid);
expansion.put(gid, totalWeight);
}
}
/*
* Check if all groups have majority
*/
int majGroupCounter = 0;
for (Entry<Long, Long> entry : expansion.entrySet()) {
Long gid = entry.getKey();
LOG.debug("Group info: {}, {}, {}", entry.getValue(), gid, groupWeight.get(gid));
if (entry.getValue() > (groupWeight.get(gid) / 2))
majGroupCounter++;
}
LOG.debug("Majority group counter: {}, {}", majGroupCounter, numGroups);
if ((majGroupCounter > (numGroups / 2))){
LOG.debug("Positive set size: {}", set.size());
return true;
} else {
LOG.debug("Negative set size: {}", set.size());
return false;
}
}
解析如下:
1.按组计算出已发送ack的服务器累计的权重值
2.判断上一步获取的权重值跟groupWeight中对应组的权重值,如果超过了它的一半,那么将通过投票的组数量加一
3.判断通过投票的组数量是否超过了group计数器numGroups的一半,如果超过一半,那就表示本轮投票通过
多数投票跟权重投票的对比
就拿文章开头的那份配置来做对比,如果是基于多数投票的验证器,那么9个zookeeper服务要想集群正常运行的话是需要(9/2 + 1) 也就是5个zookeeper服务正常运行才行的,但是基于权重的话呢,只需要两组sever中各自有两个server运行正常即可,也就是4个zookeeper服务运行正常就能保证集群运行正常