Custom IRule
In this paper, we implement a custom load balancing strategy - consistent hash through IRule interface.
The principle of consistent hash is not the focus of this paper. If you don't understand it, you can read this article Principle and implementation of consistent hash algorithm , it's very good.
Consistent hash is widely used not only in the field of load balancing, but also in many other fields.
We don't talk much nonsense, just do it!
First, we create a class under the ribbon consumer module, inherit AbstractLoadBalancerRule and implement IRule interface:
Then get the HttpServletRequest object from the context:
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder .getRequestAttributes())) .getRequest();
We use the request object to obtain information as a summary. Here we take uri as the summary:
String uri = request.getServletPath() + "?" + request.getQueryString();
We define the route method to realize the logic of finding the server:
public Server route(int hashId, List<Server> addressList){ if (CollectionUtils.isEmpty(addressList)) { return null; } TreeMap<Long, Server> address = new TreeMap<>(); addressList.forEach(e->{ //Each server uses the for loop to calculate several (8) different hash values, which are distributed in the ring. //This method is not uniform distribution. Here is just a simple example with the idea of consistent hash algorithm. for (int i = 0; i < 8; i++) { long hash = hash(e.getId() + i); address.put(hash, e); } }); long hash = hash(String.valueOf(hashId)); SortedMap<Long, Server> last = address.tailMap(hash); //When the hash value of the request Url is greater than the corresponding hashKey of any server, //Take the first node in the address if (last.isEmpty()) { return address.firstEntry().getValue(); } return last.get(last.firstKey()); }
Here, we use TreeMap to store the list of virtualized service nodes, and then use the feature of tailMap to obtain the subsequent server. If last is empty, we return the first element, because when last is empty, it means that this is the last element, which is equivalent to making it form a closed loop.
We also need a method to return the hash value:
public long hash(String key) { MessageDigest md5; try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e){ throw new RuntimeException(e); } byte [] bytes = key.getBytes(StandardCharsets.UTF_8); md5.update(bytes); byte[] digest = md5.digest(); long hashCode = (digest[2] & 0xFF << 16) | (digest[1] & 0xFF << 8) | (digest[0] & 0xFF); return hashCode & 0xffffffffL; }
The digest here is the summary we want to get, and then we generate the hashCode according to the summary. The digest is 16 bits, so let's choose the first three. Let's move the second bit forward by 16 bits, the first shift by 8 bits, and the 0 bit doesn't need to be shifted.
Here, our consistent hash algorithm has been written. Let's test it,
Let's first add the nonparametric Construction:
Then, through configuration, we specify the Ribbon's load balancing policy as our MyRule:
Next, we start three Eureka clients and ribbon consumers:
So let's test it through Postman:
Here, we can find that our request is always on the 30001 node.
Then we change the parameters of the request:
You can see that the request fell on 30000.
Then we tried to take 30000 nodes offline:
Reuse the Postman request:
You can see that no matter what the request is, it has been falling on the 30002 node this time.
Here, we have learned how to use IRule to implement custom load balancing strategy, and can apply it to our actual project.