gateway一致性哈希负载均衡

849 阅读3分钟

【导读】
在分布式微服务环境中,有时我们会基于特殊的业务场景,希望符合某个条件的请求都由同一个服务节点处理。
在 SpringCloud Loadbalancer 中只默认提供了随机(RandomLoadBalancer)轮询(RoundRobinLoadBalancer) 两种请求分配算法,如果想实现上述目标,必须自定义一致性哈希负载均衡器。

核心实现步骤

  1. 在项目配置文件中指定要使用一致性哈希负载的接口URI。
  2. 创建一致性哈希负载均衡器。
  3. 定义一个用于计算一致性哈希值的算法。
  4. 启用一致性哈希负载均衡器。

如果看了源码依然不明白实现过程,可能你要先学习一下SpringCloud的基础知识。🤣

配置接口清单

  1. 在配置文件中添加列表形式的配置项
customize:
    consistent-hashing-urls:
      - /api/member-service/members/detail
      - /api/member-service/members/list
  1. 加载配置项
@Component
@Data
@ConfigurationProperties(prefix = "customize")
public class ConsistentHashingProperties {
    /**
     * 使用一致性哈希负载算法分配服务节点的uri
     */
    private List<String> consistentHashingUrls;
}

创建一致性哈希负载均衡器

  1. 继承 RandomLoadBalancer
public class ConsistentHashingLoadBalancer extends RandomLoadBalancer
  1. 声明核心变量
/**
 * 本示例中通过会员id来计算哈希值
 */
private static final String MEMBER_ID = "memberId";
/**
 * 用于读取要使用一致性哈希负载的接口URI
 */
@Autowired
private ConsistentHashingProperties consistentHashingProperties;
  1. 计算一致性哈希值
/**
 * 使用Guava的工具包计算一致性哈希值,虽然它还是Beta版本,作为一个示例已经足够用了
 */
int memberIdHashCode = memberId.hashCode();
int mode = Hashing.consistentHash(memberIdHashCode, instances.size());
  1. 包含在配置接口清单中的请求才需要按一致性哈希算法处理
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
        RequestData clientRequest = (RequestData) requestContext.getClientRequest();
        String uri = clientRequest.getUrl().getPath();
        if (!consistentHashingProperties.getConsistentHashingUrls().contains(uri)) {
            // 没有在配置文件中指定的请求,使用默认负载算法
            return super.choose(request);
        }
        String query = clientRequest.getUrl().getQuery();
        if (StringUtils.isEmpty(query)) {
            // 没有任何参数的请求,使用默认负载算法
            return super.choose(request);
        }
        String[] pairs = query.split("&");
        String[] keyAndValue = null;
        String memberId = null;
        for (String pair : pairs) {
            keyAndValue = pair.split("=");
            if (MEMBER_ID.equals(keyAndValue[0])) {
                memberId = keyAndValue[1];
                break;
            }
        }
        if (StringUtils.isEmpty(memberId)) {
            // 参数中没有memberId的请求,使用默认负载算法
            return super.choose(request);
        }

        var supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        String finalmemberId = memberId;
        // 符合条件的请求,使用一致性哈希算法处理
        return supplier.get(request).next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, finalmemberId));
    }

启用一致性哈希负载均衡器

  1. 实例化 ConsistentHashingLoadBalancer
public class ConsistentHashingConfig {
    @Bean
    public ReactorLoadBalancer<ServiceInstance> serviceInstanceReactorLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        var clientName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new ConsistentHashingLoadBalancer(loadBalancerClientFactory.getLazyProvider(clientName, ServiceInstanceListSupplier.class), clientName);
    }
}
  1. 绑定被处理接口所在服务
@Configuration
@LoadBalancerClient(name = "member-service", configuration = ConsistentHashingConfig.class)
public class EnableCustomerLoadBalancer {
    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}

源码

  1. 获取地址 github.com/mojiayi/moj…
  2. 技术栈
  • JDK 11
  • Maven 3.8.1
  • SpringCloud 2020.0.1
  • SpringCloud Alibaba 2021.1
  • SpringBoot 2.4.2
  • Gateway 3.0.1
  • guava 31.0.1-jre

总结

使用一致性哈希算法处理网关层的负载均衡,本质上抛弃了分布式系统可灵活快速水平扩展的优势。一般情况下,一个业务服务部署N个节点后,每个节点都是无状态的。所以,SpringCloud官方并不提供一致性哈希算法的负载均衡。
依托SpringCloud已有的设计思路,为自己的具体业务场景设计和实现了自己的一致性哈希负载器。