Consistent hash ring algorithm for load balancing

Posted by podja on Mon, 14 Feb 2022 06:39:51 +0100

preface

Now there is a scenario: when a client accesses the server, it can only access one machine all the time, because some user data exists on the server. If you access other servers, the user data will be lost. How to use algorithms to solve this problem?

Consistent hash ring algorithm

base


The ring structure here is abstracted from our imagination. The simplest idea is to give a request, use hash function and other operations to calculate its corresponding server location, and then allocate it.
How do clients map to nodes on the hash ring?

For example, there is a userId =1 to request:
After calculating the hash value, it falls between P1 and P4. The hash ring processing rule is to find the nearest node larger than it. The hash ring here is an ordered ring, increasing in order: P1 < P2 < P3 < P4, then the nearest node larger than userId =1 is P1, and it will be assigned to P1 server.

Which data structure in Java can meet this requirement? Complete automatic sorting (for example, after a number is entered into the set, the structure can sort the new and previous set elements. Here, TreeMap is used)

Variant: interspersed with virtual nodes to share the request pressure

Think about a question: if at a certain peak time, all requests reach P1-P
4 in this area, all servers will hit P1. P1 bears a lot of traffic, which may lead to downtime. At the same time, other servers may be idle. This is unreasonable. We can use a lot of virtual nodes, which can be interspersed with many virtual nodes on the original basis, similar to the Monkey King pulling out a hair to separate himself, In this way, a large amount of traffic can be distributed to different servers, so that the pressure is evenly distributed and hashed:

Data structure implementation of consistent hash ring: TreeMap

The bottom layer of TreeMap adopts red and black trees,


The red black tree here will automatically sort the inserted data. There is a tailMap() function in the TreeMap api, which inputs a fromKey and outputs a SortedMap ordered Map:

 SortedMap<Integer,String> subMap = virtualNodes.tailMap(2);
 Integer nodeIndex = subMap.firstKey();//Get the first element

You can get the data of indexes 2, 3, 4 and 5. String is its corresponding value.
To put it simply: you can find the subtree on the right of 2 (that is, the subtree greater than or equal to 2) through tailMap(), and then use firstKey() to get the first element larger than him.

This corresponds to our consistent hash ring, and you can get its nearest server node larger than it

Consider the boundary: if the request is calculated to be larger than P4, it can directly return to P1 (ring)

code

//Implementation of consistent hash using treemap
public class ConsistentHash {
    private static TreeMap<Integer,String> virtualNodes = new TreeMap();
    private static final int VIRTUAL_NODES = 160;//Number of virtual nodes

    //Preprocessing hash ring
    static {
        //Add virtual nodes to each real node and hash according to hash algorithm
        for(String ip: ServerIps.LIST) {
            for(int i = 0; i < VIRTUAL_NODES; i++) {
                int hash = getHash(ip+"VN"+i);
                virtualNodes.put(hash,ip);
            }
        }
    }

    public static String getServer(String clientInfo) {
        int hash = getHash(clientInfo);
        //Obtain a sub red black tree greater than the Hash value
        SortedMap<Integer,String> subMap = virtualNodes.tailMap(hash);
        //Gets the smallest element of the subtree
        Integer nodeIndex = subMap.firstKey();

        //No subtree greater than this element rounds the first element of the tree
        if (nodeIndex == null) {
            nodeIndex = virtualNodes.firstKey();
        }
        return virtualNodes.get(nodeIndex);
    }


    //hash algorithm
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++) {
            hash = (hash^str.charAt(i))*p;
            hash +=hash <<13;
            hash ^=hash >>7;
            hash +=hash <<3;
            hash ^=hash >>17;
            hash +=hash <<5;
            //If the calculated value is negative, take its absolute value
            if(hash < 0) {
                hash = Math.abs(hash);
            }
        }
        return hash;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            System.out.println(getServer("userId" + i));
        }
    }
}
//Consistency hash is realized by treemap
public class ConsistentHash {
    private static TreeMap<Integer,String> virtualNodes = new TreeMap();
    private static final int VIRTUAL_NODES = 160;//Number of virtual nodes

    //Preprocessing hash ring
    static {
        //Add virtual nodes to each real node and hash according to hash algorithm
        for(String ip: ServerIps.LIST) {
            for(int i = 0; i < VIRTUAL_NODES; i++) {
                int hash = getHash(ip+"VN"+i);
                virtualNodes.put(hash,ip);
            }
        }
    }

    public static String getServer(String clientInfo) {
        int hash = getHash(clientInfo);
        //Obtain a sub red black tree greater than the Hash value
        SortedMap<Integer,String> subMap = virtualNodes.tailMap(hash);
        //Gets the smallest element of the subtree
        Integer nodeIndex = subMap.firstKey();

        //No subtree greater than this element rounds the first element of the tree
        if (nodeIndex == null) {
            nodeIndex = virtualNodes.firstKey();
        }
        return virtualNodes.get(nodeIndex);
    }


    //hash algorithm
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++) {
            hash = (hash^str.charAt(i))*p;
            hash +=hash <<13;
            hash ^=hash >>7;
            hash +=hash <<3;
            hash ^=hash >>17;
            hash +=hash <<5;
            //If the calculated value is negative, take its absolute value
            if(hash < 0) {
                hash = Math.abs(hash);
            }
        }
        return hash;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            System.out.println(getServer("userId" + i));
        }
    }
}


It can be seen that the running results are relatively hash, and the pressure on the server is relatively small.
Here is also a test to ensure that for the same userId, each request will reach the same server, which is reasonable (the results are consistent every time).

Topics: Load Balance Algorithm