spring cloud gateway + nacos 灰度发布
原理
- 在客户端请求时的 header 带入 一个标签,如: svc_version
- 服务器应用 启动的时候 打上标签,可以和客户端的属性一致,如 svc_version
- 请求到网关的时候,网关负载过滤带 svc_version 属性的服务器应用列表
- 返回已经匹配客户端 svc_version 的服务器应用 列表
- 在这些列表中请求转发客户端请求
应用列表
- nacos-user : 普通应用
- nacos-gateway
nacos-gateway
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
注意一定要引入 spring-cloud-loadbalancer
!!
实现 灰度负载LoadBalancer
public class GreyRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final String GREP_HEADER = "svc_version";
private static final Log log = LogFactory.getLog(GreyRoundRobinLoadBalancer.class);
final AtomicInteger position;
final String serviceId;
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
*/
public GreyRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
}
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
* @param seedPosition Round Robin element position marker
*/
public GreyRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}
@SuppressWarnings("rawtypes")
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request)
.flatMap(list -> {
RequestDataContext context = (RequestDataContext) request.getContext();
String version = context.getClientRequest().getHeaders().getFirst(GREP_HEADER);
boolean b = StringUtils.hasText(version) ? true : false;
List<ServiceInstance> collect = list.stream().filter(instance -> {
if (b) {
return instance.getMetadata().containsKey(GREP_HEADER) && instance.getMetadata().get(GREP_HEADER).equals(version);
}
return !instance.getMetadata().containsKey(GREP_HEADER);
}).collect(Collectors.toList());
return Mono.justOrEmpty(collect);
})
.next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// Ignore the sign bit, this allows pos to loop sequentially from 0 to
// Integer.MAX_VALUE
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
配置
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class GreyLoadBalancerClientConfiguration {
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 173827465;
@Bean
// @Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER)
public ReactorLoadBalancer<ServiceInstance> greyLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new GreyRoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
向容器注入自定义 LoadBalancer:
@Configuration
@LoadBalancerClients(defaultConfiguration = GreyLoadBalancerClientConfiguration.class)
public class CusLoadBalancerClientConfiguration {
}
@LoadBalancerClients, 重要!!,给所有的负载都加上自定义的灰度负载器
使用
启用应用nacos-user
, 一个打了标签,一个没有打标签:
在客户端请求网关时,headers带上 svc_version=1.0, 那么请求就会打到 5558这个应用
参考 nacos loadbalancer:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnLoadBalancerNacos
@ConditionalOnNacosDiscoveryEnabled
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)
public class LoadBalancerNacosAutoConfiguration {
}
good luck!