(七)zookeeper的Leader选举与事务投票过半策略之QuorumHierarchical

  • 基于权重实现的投票验证器: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服务运行正常就能保证集群运行正常

猜你喜欢

转载自blog.csdn.net/long9870/article/details/93737461
今日推荐