【导读】
在分布式微服务环境中,有时我们会基于特殊的业务场景,希望符合某个条件的请求都由同一个服务节点处理。
在 SpringCloud Loadbalancer 中只默认提供了随机(RandomLoadBalancer) 和轮询(RoundRobinLoadBalancer) 两种请求分配算法,如果想实现上述目标,必须自定义一致性哈希负载均衡器。
核心实现步骤
- 在项目配置文件中指定要使用一致性哈希负载的接口URI。
- 创建一致性哈希负载均衡器。
- 定义一个用于计算一致性哈希值的算法。
- 启用一致性哈希负载均衡器。
如果看了源码依然不明白实现过程,可能你要先学习一下SpringCloud的基础知识。🤣
配置接口清单
- 在配置文件中添加列表形式的配置项
customize:
consistent-hashing-urls:
- /api/member-service/members/detail
- /api/member-service/members/list
- 加载配置项
@Component
@Data
@ConfigurationProperties(prefix = "customize")
public class ConsistentHashingProperties {
/**
* 使用一致性哈希负载算法分配服务节点的uri
*/
private List<String> consistentHashingUrls;
}
创建一致性哈希负载均衡器
- 继承 RandomLoadBalancer
public class ConsistentHashingLoadBalancer extends RandomLoadBalancer
- 声明核心变量
/**
* 本示例中通过会员id来计算哈希值
*/
private static final String MEMBER_ID = "memberId";
/**
* 用于读取要使用一致性哈希负载的接口URI
*/
@Autowired
private ConsistentHashingProperties consistentHashingProperties;
- 计算一致性哈希值
/**
* 使用Guava的工具包计算一致性哈希值,虽然它还是Beta版本,作为一个示例已经足够用了
*/
int memberIdHashCode = memberId.hashCode();
int mode = Hashing.consistentHash(memberIdHashCode, instances.size());
- 包含在配置接口清单中的请求才需要按一致性哈希算法处理
@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));
}
启用一致性哈希负载均衡器
- 实例化 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);
}
}
- 绑定被处理接口所在服务
@Configuration
@LoadBalancerClient(name = "member-service", configuration = ConsistentHashingConfig.class)
public class EnableCustomerLoadBalancer {
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}
源码
- 获取地址 github.com/mojiayi/moj…。
- 技术栈
- 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已有的设计思路,为自己的具体业务场景设计和实现了自己的一致性哈希负载器。