Dubbo load balancing: implementation analysis of consistency Hash

Keywords: Programming Dubbo

Author: FserSuN

Source: https://blog.csdn.net/Revivedsun/article/details/71022871

LoadBalance is responsible for selecting a specific Invoker from multiple invokers for this call to share the pressure. The LoadBalance structure in Dubbo is shown in the figure below.

com.alibaba.dubbo.rpc.cluster.LoadBalance 
Interface provides
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException; 
Through this method, nodes are selected.
com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance 
Implements some common methods and defines abstract methods
protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation); 
This method is implemented by a specific load balancing implementation class.

Consistent hash load balancing configuration

There are four specific load balancing implementation classes. They are random, rotation training, least active and consistent Hash Consistent hash load balancing configuration

Configuration such as:

<dubbo:service interface="..." loadbalance="consistenthash" />
Or:

<dubbo:reference interface="..." loadbalance="consistenthash" />
Or:

<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="consistenthash"/>
</dubbo:service>
Or:

<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="consistenthash"/>
</dubbo:reference

Consistency Hash load balancing involves two main configuration parameters: hash.arguments and hash.nodes.

Hash.arguments: when a call is made, the key is generated according to the parameters of the calling method, and the calling node is selected through the consistency hash algorithm according to the key. For example, call the method invoke(String s1,String s2); if hash.arguments is 1 (the default), only the parameter 1 (s1) of invoke is taken to generate the hashCode.

hash.nodes: is the number of copies of the node.

Only the first parameter Hash is selected by default. If you want to modify it, please configure
<dubbo:parameter key="hash.arguments" value="0,1" />

160 virtual nodes are used by default. If you want to modify them, please configure
<dubbo:parameter key="hash.nodes" value="320" />

Implementation analysis of consistency Hash in Dubbo

The consistency hash of dubbo is implemented by the ConsistentHashLoadBalance class.

ConsistentHashLoadBalance internally defines the ConsistentHashSelector class, and finally selects nodes through this class. The doSelect method implemented by ConsistentHashLoadBalance uses the ConsistentHashSelector object created to select nodes.

The implementation of doSelect is as follows. When the method is called, it is created if the selector does not exist. Then select the node through the select method of ConsistentHashSelector.

@SuppressWarnings("unchecked")
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    // Get call method name
    String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
    // Generate call list hashCode
    int identityHashCode = System.identityHashCode(invokers);
    // Get the consistency hash selector by calling the method named key
    ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
    // Create a new selector if it does not exist
    if (selector == null || selector.getIdentityHashCode() != identityHashCode) {
        // All virtual nodes are generated when ConsistentHashSelector is created
        selectors.put(key, new ConsistentHashSelector<T>(invokers, invocation.getMethodName(), identityHashCode));
        // Get selector
        selector = (ConsistentHashSelector<T>) selectors.get(key);
    }
    // Select node
    return selector.select(invocation);
}

ConsistentHashSelector creates replicaNumber virtual nodes inside the constructor and stores them in TreeMap. Then the key is generated according to the parameters of the calling method, and a node is selected in the TreeMap for calling.

private static final class ConsistentHashSelector<T> {
    private final TreeMap<Long, Invoker<T>> virtualInvokers; // Virtual node

    private final int                       replicaNumber;   // Copy number 

    private final int                       identityHashCode;// hashCode

    private final int[]                     argumentIndex;   // Parameter index array

    public ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
        // Create TreeMap to save nodes
        this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
        // Generate call node HashCode
        this.identityHashCode = System.identityHashCode(invokers);
        // Get Url 
        // dubbo://169.254.90.37:20880/service.DemoService?anyhost=true&application=srcAnalysisClient&check=false&dubbo=2.8.4&generic=false&interface=service.DemoService&loadbalance=consistenthash&methods=sayHello,retMap&pid=14648&sayHello.timeout=20000&side=consumer&timestamp=1493522325563
        URL url = invokers.get(0).getUrl();
        // Get the configured number of nodes, if not set, use the default value of 160
        this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
        // Get the parameter array index that needs to be hashed. The first parameter is hashed by default
        String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
        argumentIndex = new int[index.length];
        for (int i = 0; i < index.length; i ++) {
            argumentIndex[i] = Integer.parseInt(index[i]);
        }
        // Create virtual node
        // Generate replicaNumber virtual nodes for each invoker and store them in TreeMap
        for (Invoker<T> invoker : invokers) {

            for (int i = 0; i < replicaNumber / 4; i++) {
                // According to md5 algorithm, a message summary is generated for every 4 nodes, which is 16 bytes and 128 bits long.
                byte[] digest = md5(invoker.getUrl().toFullString() + i);
                // Then, the 128 bits are divided into four parts, 0-31, 32-63, 64-95, 95-128, and four 32 bits are generated, which are stored in long. The high 32 bits of long are all 0
                // As the key of the virtual node.
                for (int h = 0; h < 4; h++) {
                    long m = hash(digest, h);
                    virtualInvokers.put(m, invoker);
                }
            }
        }
    }

    public int getIdentityHashCode() {
        return identityHashCode;
    }

    // Select node
    public Invoker<T> select(Invocation invocation) {
        // Generate Key according to calling parameters
        String key = toKey(invocation.getArguments());
        // Generate message summary based on this parameter
        byte[] digest = md5(key);
        //Call hash(digest, 0) to convert message summary to hashcode. Here, only 0-31 bits are taken to generate hashcode
        //Call the sekectForKey method to select the node.
        Invoker<T> invoker = sekectForKey(hash(digest, 0));
        return invoker;
    }

    private String toKey(Object[] args) {
        StringBuilder buf = new StringBuilder();
        // Because hash.arguments is not configured, only the first parameter of the method is taken as the key
        for (int i : argumentIndex) {
            if (i >= 0 && i < args.length) {
                buf.append(args[i]);
            }
        }
        return buf.toString();
    }

    //Select nodes according to hashCode
    private Invoker<T> sekectForKey(long hash) {
        Invoker<T> invoker;
        Long key = hash;
        // If the HashCode is directly the same as the key of a virtual node, the node will be returned directly
        if (!virtualInvokers.containsKey(key)) {
            // If not, find the node corresponding to the last key.
            SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key);
            // If it exists, return, for example, the position of hashCode in [1] in the figure
            // If it does not exist, for example, the hashCode falls in the position of [2], then select the first node in the treeMap
            // Use the firstKey method of TreeMap to select the minimum upper bound.
            if (tailMap.isEmpty()) {
                key = virtualInvokers.firstKey();
            } else {

                key = tailMap.firstKey();
            }
        }
        invoker = virtualInvokers.get(key);
        return invoker;
    }

    private long hash(byte[] digest, int number) {
        return (((long) (digest[3 + number * 4] & 0xFF) << 24)
                | ((long) (digest[2 + number * 4] & 0xFF) << 16)
                | ((long) (digest[1 + number * 4] & 0xFF) << 8) 
                | (digest[0 + number * 4] & 0xFF)) 
                & 0xFFFFFFFFL;
    }

    private byte[] md5(String value) {
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        md5.reset();
        byte[] bytes = null;
        try {
            bytes = value.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        md5.update(bytes);
        return md5.digest();
    }
}

In the above code, the hash(byte[] digest, int number) method is used to generate the hashCode. This function converts the generated result to a long class, because the generated result is a 32-digit number, which may result in a negative number if saved with int. The range of hashCode of the logic ring generated by consistency hash is between 0 - max value. Therefore, it is a positive integer, so you need to cast it to long type to avoid negative number.

The method of node selection is select. Finally, the node is selected through the sekectForKey method.

// Select node
public Invoker<T> select(Invocation invocation) {
    // Generate Key according to calling parameters
    String key = toKey(invocation.getArguments());
    // Generate message summary based on this parameter
    byte[] digest = md5(key);
    //Call hash(digest, 0) to convert message summary to hashcode. Here, only 0-31 bits are taken to generate hashcode
    //Call the sekectForKey method to select the node.
    Invoker<T> invoker = sekectForKey(hash(digest, 0));
    return invoker;
}

The implementation of the sekectForKey method is as follows.

private Invoker<T> sekectForKey(long hash) {
     Invoker<T> invoker;
     Long key = hash;
     // If the HashCode is directly the same as the key of a virtual node, the node will be returned directly
     if (!virtualInvokers.containsKey(key)) {
         // If not, find the node corresponding to the last key.
         SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key);
         // If it exists, return, for example, the position of hashCode in [1] in the figure
         // If it does not exist, for example, the hashCode falls in the position of [2], then select the first node in the treeMap
         // Use the firstKey method of TreeMap to select the minimum upper bound.
         if (tailMap.isEmpty()) {
             key = virtualInvokers.firstKey();
         } else {
						 key = tailMap.firstKey();
				 }
     }
    invoker = virtualInvokers.get(key);
    return invoker;
}

When selecting, if the hashcode is directly the same as the key of a virtual node, the node will be returned directly. For example, the hashcode falls on a node (represented by a circle). If not, find the node corresponding to the last key. For example, the key for selection falls to the position marked in Figure 1. Due to the use of TreeMap storage, the location where the key is located may not find the minimum upper bound, such as the location marked in Figure 2. Then you need to return the minimum value in the TreeMap (to form a logical ring structure, if it cannot be found, the node at the beginning will be returned).

If you like my article, you can pay attention to the personal subscription number. Welcome to leave a message and communicate at any time.

Posted by hesyar on Thu, 20 Feb 2020 00:41:19 -0800