本文已参与「新人创作礼」活动,一起开启掘金创作之路。
自定义IRule
我们可以通过实现IRule接口,来实现一个自定义的负载均衡策略。接下来我们实现一个一致性哈希的负载均衡策略,一致性哈希的负载均衡策略不仅在负载均衡的领域,在很多其他的领域都有广泛的应用。我们废话不多说,直接开干!
/**
* 一致性哈希的负载均衡策略
*
* @author liangks
* @date 2022/2/23
*/
@NoArgsConstructor
public class MyRule extends AbstractLoadBalancerRule implements IRule {
/**
* 虚拟节点的数目,这里写死,一个真实结点对应10个虚拟节点
*/
protected static final int VIRTUAL_NODES = 10;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
// 使用uri作为摘要
String uri = request.getServletPath() + "?" + request.getQueryString();
return route(uri.hashCode(), getLoadBalancer().getAllServers());
}
/**
* @param hashId hash摘要
* @param allServers 服务节点
* @return Server
*/
public Server route(int hashId, List<Server> allServers) {
if (CollectionUtils.isEmpty(allServers)) {
return null;
}
TreeMap<Long, Server> serverRing = new TreeMap<>();
// 服务上环
allServers.forEach(server -> {
// 通过采取虚拟节点的方法,一个真实结点不再固定在Hash换上的某个点,而是大量地分布在整个Hash环上,这样即使上线、下线服务器,也不会造成整体的负载不均衡。
for (int i = 0; i < VIRTUAL_NODES; i++) {
long hash = hash(server.getId() + i);
serverRing.put(hash, server);
}
});
long hash = hash(String.valueOf(hashId));
// 返回最近的服务组
SortedMap<Long, Server> last = serverRing.tailMap(hash);
// 为空代表到了末尾,取第一个即可
if (last.isEmpty()) {
return serverRing.firstEntry().getValue();
}
// 不为空则取最近的一个
return last.get(last.firstKey());
}
/**
* 使用FNV1_32_HASH算法计算Hash值
*/
public int hash(String key) {
final int p = 16777619;
int hash = (int) 2166136261L;
for (int i = 0; i < key.length(); i++) {
hash = (hash ^ key.charAt(i)) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
// 如果算出来的值为负数则取其绝对值
if (hash < 0) {
hash = Math.abs(hash);
}
return hash;
}