——基于 Nacos + Spring Cloud LoadBalancer 的微服务灰度策略落地
全链路灰度发布:在微服务调用链中,让特定特征的流量(如打了标签的用户、携带特定Header的请求)只路由到标记灰度的服务实例,实现对整个调用链的隔离测试,而非单个服务。
核心原理:
技术栈选择:
服务 | 端口 | 版本 |
gateway-service | 8080 | 网关入口 |
order-service | 8081 | 稳定版 |
order-service | 8082 | 灰度版 |
product-service | 8083 | 稳定版 |
product-service | 8084 | 灰度版 |
目标:携带Header version: gray的请求,全程访问灰度实例。
父POM依赖管理:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2022.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2022.0.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
子模块依赖示例(order-service):
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Web 等其他依赖 -->
</dependencies>
Order-Service 稳定版配置 (application.yml):
server:
port: 8081
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
metadata:
version: stable
灰度版配置 (application-gray.yml):
server:
port: 8082
spring:
cloud:
nacos:
discovery:
metadata:
version: gray
Product-Service 同理,分别启动灰度与稳定版实例。
配置 LoadBalancer:
@Configuration
@LoadBalancerClient(name = "order-service", configuration = GrayLoadBalancerConfiguration.class)
@LoadBalancerClient(name = "product-service", configuration = GrayLoadBalancerConfiguration.class)
public class GrayLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> grayRoundRobinLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new GrayVersionRoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
核心灰度算法实现:
public class GrayVersionRoundRobinLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
private AtomicInteger position = new AtomicInteger(0);
private final Supplier<ServiceInstanceListSupplier> serviceInstanceListSupplier;
private final String serviceId;
public GrayVersionRoundRobinLoadBalancer(Supplier<ServiceInstanceListSupplier> supplier, String serviceId) {
this.serviceInstanceListSupplier = supplier;
this.serviceId = serviceId;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplier.get();
return supplier.get(request).next()
.map(instances -> getInstanceResponse(instances, request));
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {
if (instances.isEmpty()) return new EmptyResponse();
String requestVersion = getRequestVersion(request); // 从Header或ThreadLocal获取
List<ServiceInstance> matched = instances.stream()
.filter(i -> requestVersion.equals(i.getMetadata().get("version")))
.collect(Collectors.toList());
if (matched.isEmpty()) {
matched = instances.stream()
.filter(i -> "stable".equals(i.getMetadata().get("version")) || i.getMetadata().get("version") == null)
.collect(Collectors.toList());
}
int pos = Math.abs(this.position.incrementAndGet() % matched.size());
return new DefaultResponse(matched.get(pos));
}
private String getRequestVersion(Request request) {
return "gray"; // 简化示例,实际从上下文动态获取
}
}
网关 GlobalFilter:
@Component
public class GrayHeaderFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String version = exchange.getRequest().getHeaders().getFirst("version");
if ("gray".equals(version)) {
exchange.getAttributes().put("version", version);
BaggageField baggageField = BaggageField.getByName(exchange, "version");
if (baggageField != null) baggageField.updateValue(version);
}
return chain.filter(exchange);
}
}
微服务内部:
Order-Service 测试接口:
@RestController
public class TestController {
@Value("${server.port}")
private String port;
@GetMapping("/port")
public String getPort() {
return "Order Service is running on port: " + port;
}
}
验证请求:
