Spring Cloud Gateway灰度发布
一、灰度发布场景实现方案
1. 基于Header的灰度路由
@Configuration
public class GrayReleaseConfig {
@Bean
public RouteLocator grayRoute(RouteLocatorBuilder builder) {
return builder.routes()
// 灰度路由(带特定header的请求路由到v2版本)
.route("gray_v2", r -> r.header("X-Gray-Release", "true")
.and().path("/product/**")
.filters(f -> f.rewritePath("/product/(?<segment>.*)", "/v2/product/${segment}"))
.uri("lb://product-service")
.metadata("version", "v2"))
// 默认路由(其他请求走v1版本)
.route("default_v1", r -> r.path("/product/**")
.filters(f -> f.rewritePath("/product/(?<segment>.*)", "/v1/product/${segment}"))
.uri("lb://product-service"))
.build();
}
}
2. 基于权重的灰度分流
@Configuration
public class WeightedRouteConfig {
@Bean
public RouteLocator weightedRoutes(RouteLocatorBuilder builder) {
return builder.routes()
// v1版本接收80%流量
.route("weight_v1", r -> r.weight("product-service", 80)
.and().path("/order/**")
.uri("lb://product-service/v1"))
// v2版本接收20%流量
.route("weight_v2", r -> r.weight("product-service", 20)
.and().path("/order/**")
.uri("lb://product-service/v2"))
.build();
}
}
3. 基于用户ID的灰度分组
@Component
@Order(-1)
public class GrayUserFilter implements GlobalFilter {
private final Set<String> grayUsers = Set.of("1001", "1002", "1005");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-ID");
if (grayUsers.contains(userId)) {
exchange.getAttributes().put("GRAY_TAG", true);
}
return chain.filter(exchange);
}
}
// 在路由配置中使用自定义断言
@Bean
public RouteLocator userGrayRoute(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.asyncPredicate(exchange ->
Mono.just(exchange.getAttribute("GRAY_TAG") == Boolean.TRUE)
)
.uri("lb://product-service/v2"))
.build();
}
二、请求链路追踪场景实现方案
1. 集成Sleuth+Zipkin
# application.yml
spring:
sleuth:
sampler:
probability: 1.0
zipkin:
base-url: http://zipkin-server:9411
sender:
type: web
2. 自定义TraceID透传过滤器
@Component
public class TraceFilter implements GlobalFilter {
private static final String TRACE_HEADER = "X-Trace-ID";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String traceId = exchange.getRequest().getHeaders().getFirst(TRACE_HEADER);
if (traceId == null) {
traceId = UUID.randomUUID().toString();
}
// 向下游传递
ServerHttpRequest request = exchange.getRequest().mutate()
.header(TRACE_HEADER, traceId)
.build();
// 记录到日志上下文
MDC.put("traceId", traceId);
return chain.filter(exchange.mutate().request(request).build())
.doFinally(signalType -> MDC.remove("traceId"));
}
}
3. 请求日志增强配置
@Configuration
public class LoggingConfig {
@Bean
public GlobalFilter loggingFilter() {
return (exchange, chain) -> {
long startTime = System.currentTimeMillis();
return chain.filter(exchange).doFinally(signal -> {
String path = exchange.getRequest().getPath().toString();
int status = exchange.getResponse().getStatusCode() != null ?
exchange.getResponse().getStatusCode().value() : 500;
log.info("[{}] {} {} {}ms",
exchange.getAttribute("traceId"),
exchange.getRequest().getMethod(),
path,
System.currentTimeMillis() - startTime);
});
};
}
}
4. 全链路监控看板(需配合Prometheus+Grafana)
@Configuration
public class MetricsConfig {
@Bean
public GatewayMetricsFilter metricsFilter(MeterRegistry registry) {
return new GatewayMetricsFilter(registry);
}
@Bean
public RouteLocator monitoredRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("monitored_route", r -> r.path("/api/**")
.filters(f -> f.prefixPath("/monitored")
.filter(metricsFilter()))
.uri("lb://monitored-service"))
.build();
}
}
三、关键实现要点说明
-
灰度发布核心逻辑:
- 使用
Weight
谓词时需确保所有分组的weight总和为100 - 灰度标识建议采用请求头而非Cookie(避免浏览器缓存影响)
- 使用
-
链路追踪最佳实践:
// 在logback-spring.xml中配置traceId显示 <pattern>%d{yyyy-MM-dd HH:mm:ss} [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
-
性能优化建议:
// 异步线程上下文传递 @Bean public ReactorHook traceContextHook() { return new ReactorHook() { @Override public Context onOperator(Context ctx) { return ctx.put("traceId", MDC.get("traceId")); } }; }