灰度发布技术解读
2025/12/3大约 8 分钟
灰度发布技术实现
1. 技术概述
灰度发布是一种平滑的发布策略,允许新功能在部分用户群体中进行测试,验证稳定性后再全量发布。淘票票项目采用基于Spring Cloud和Nacos的灰度发布方案,通过服务注册中心的元数据和请求头传递灰度标识,实现服务间的灰度路由。
2. 灰度发布的优势
- 降低风险:新功能先在小范围内测试,避免全量发布带来的系统性风险
- 快速验证:通过真实用户反馈快速验证功能效果和性能
- 服务隔离:通过Nacos元数据标识灰度服务,实现服务间的灰度隔离
- 灰度传递:灰度标识通过请求头和ThreadLocal在服务调用链中传递
- 快速回滚:发现问题可以立即下线灰度服务,影响范围小
3. 灰度发布架构
淘票票项目灰度发布框架由三个核心模块组成:
- 基础框架(taopiaopiao-service-gray-transition-base-framework):提供核心过滤和负载均衡功能
- Gateway框架(taopiaopiao-service-gray-transition-gateway-framework):处理网关层的灰度标识传递
- WebMVC框架(taopiaopiao-service-gray-transition-webmvc-framework):处理业务服务层的灰度标识处理
4. 核心实现方案
4.1 灰度标识定义
项目中使用以下常量定义灰度相关信息:
public class Constant {
// 灰度标识常量
public static final String GRAY_FLAG_TRUE = "true";
public static final String GRAY_FLAG_FALSE = "false";
public static final String GRAY_PARAMETER = "gray"; // 请求头中的灰度参数名
public static final String SERVER_GRAY = "${spring.cloud.nacos.discovery.metadata.gray:false}"; // 服务自身的灰度标识
}4.2 灰度过滤器实现
ServerGrayFilter是整个灰度发布的核心,负责根据灰度标识过滤服务实例:
@Slf4j
public class ServerGrayFilter extends AbstractServerFilter {
/**
* 此服务的灰度标识
*/
@Value(SERVER_GRAY)
private String serverGray;
private final ContextHandler contextHandler;
private final Map<String,String> map = new HashMap<>();
public ServerGrayFilter(ContextHandler contextHandler){
this.contextHandler = contextHandler;
this.map.put(GRAY_FLAG_FALSE, GRAY_FLAG_FALSE);
this.map.put(GRAY_FLAG_TRUE, GRAY_FLAG_TRUE);
}
@Override
public boolean doFilter(List<? extends ServiceInstance> servers, ServiceInstance server) {
boolean result;
try {
// 1. 从请求头或ThreadLocal中获取灰度标识
String grayFromRequest = Optional.ofNullable(contextHandler.getValueFromHeader(GRAY_PARAMETER))
.filter(StringUtil::isNotEmpty)
.orElseGet(() -> BaseParameterHolder.getParameter(GRAY_PARAMETER));
// 2. 如果没有灰度标识,则使用服务默认标识
grayFromRequest = Optional.ofNullable(grayFromRequest).filter(StringUtil::isNotEmpty).orElse(serverGray);
// 3. 获取服务实例的灰度标识(从Nacos元数据中)
NacosServiceInstance nacosServiceInstance = (NacosServiceInstance)server;
String grayFromMetaData = Optional.ofNullable(nacosServiceInstance.getMetadata())
.filter(CollectionUtil::isNotEmpty)
.map(metadata -> metadata.get(GRAY_PARAMETER))
.filter(StringUtil::isNotEmpty)
.orElse(GRAY_FLAG_FALSE);
// 4. 标准化灰度标识
grayFromMetaData = Optional.ofNullable(map.get(grayFromMetaData.toLowerCase())).orElse(GRAY_FLAG_FALSE);
grayFromRequest = Optional.ofNullable(map.get(grayFromRequest.toLowerCase())).orElse(GRAY_FLAG_FALSE);
// 5. 匹配灰度标识
result = grayFromMetaData.equalsIgnoreCase(grayFromRequest);
// 6. 降级策略:如果请求需要灰度服务,但没有可用的灰度实例,则使用普通服务
if (!result && grayFromRequest.equalsIgnoreCase(GRAY_FLAG_TRUE)) {
if (CollectionUtil.isEmpty(servers)) {
throw new TaoPiaoPiaoFrameException(BaseCode.SERVER_LIST_NOT_EXIST);
}
Map<String,String> instanceGrayMap = new HashMap<>(servers.size());
for (ServiceInstance serviceInstance : servers) {
NacosServiceInstance instance = (NacosServiceInstance)serviceInstance;
String balanceGray = instance.getMetadata().get(GRAY_PARAMETER);
if (StringUtil.isEmpty(balanceGray) || Objects.isNull(instanceGrayMap.get(balanceGray.toLowerCase()))) {
balanceGray = GRAY_FLAG_FALSE;
}
instanceGrayMap.put(balanceGray, balanceGray);
}
// 如果没有灰度服务实例,则允许使用普通服务
if(Objects.isNull(instanceGrayMap.get(GRAY_FLAG_TRUE))) {
result = true;
}
}
} catch (Exception e) {
result = false;
log.error("CustomAwarePredicate#apply error", e);
}
return result;
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE; // 设置最高优先级
}
}4.3 上下文处理器
项目提供两种上下文处理器实现,用于不同场景下获取请求头信息:
WebMVC上下文处理器:
public class WebMvcContextHandler implements ContextHandler {
@Override
public String getValueFromHeader(final String name) {
HttpServletRequest request = getHttpServletRequest();
if (Objects.nonNull(request)) {
return request.getHeader(name);
}
return null;
}
public HttpServletRequest getHttpServletRequest() {
ServletRequestAttributes attributes = getRestAttributes();
if (attributes == null) {
return null;
}
return attributes.getRequest();
}
public ServletRequestAttributes getRestAttributes() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return null;
}
return (ServletRequestAttributes) requestAttributes;
}
}Gateway上下文处理器:
public class GatewayContextHandler implements ContextHandler {
@Override
public String getValueFromHeader(final String name) {
return Optional.ofNullable(getServerHttpRequest())
.map(request -> request.getHeaders().getFirst(name))
.orElse(null);
}
public ServerWebExchange getExchange() {
return GatewayContextHolder.getCurrentGatewayContext().getExchange();
}
public ServerHttpRequest getServerHttpRequest() {
return Optional.ofNullable(getExchange()).map(ServerWebExchange::getRequest).orElse(null);
}
}4.4 线程参数传递
使用ThreadLocal实现服务间的灰度标识传递:
public class BaseParameterHolder {
/**
* 使用ThreadLocal存储每个线程的参数映射表
* ThreadLocal确保每个线程拥有独立的变量副本,实现线程间数据隔离
*/
private static final ThreadLocal<Map<String, String>> THREAD_LOCAL_MAP = ThreadLocal<>();
/**
* 设置线程参数
*/
public static void setParameter(String name, String value) {
Map<String, String> map = THREAD_LOCAL_MAP.get();
if (map == null) {
map = new HashMap<>(64);
}
map.put(name, value);
THREAD_LOCAL_MAP.set(map);
}
/**
* 获取线程参数值
*/
public static String getParameter(String name) {
return Optional.ofNullable(THREAD_LOCAL_MAP.get()).map(map -> map.get(name)).orElse(null);
}
/**
* 移除当前线程的参数映射表
* 调用ThreadLocal的remove方法防止内存泄漏
*/
public static void removeParameterMap(){
THREAD_LOCAL_MAP.remove();
}
}4.5 网关层灰度处理
Gateway模块中的过滤器负责保存和清理上下文:
保存上下文过滤器:
public class GatewayWorkRouteFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
// 保存ServerWebExchange到ThreadLocal中
GatewayContextHolder.getCurrentGatewayContext().setExchange(exchange);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE; // 最高优先级
}
}清理上下文过滤器:
public class GatewayWorkClearFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
// 请求结束时清理ThreadLocal,防止内存泄漏
GatewayContextHolder.removeCurrentGatewayContext();
return chain.filter(exchange);
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE - 1; // 最低优先级,确保在最后执行
}
}4.6 负载均衡配置
通过自定义负载均衡器实现灰度服务的选择:
@LoadBalancerClients(defaultConfiguration = {EnhanceLoadBalancerClientConfiguration.class, ReactiveSupportConfiguration.class, BlockingSupportConfiguration.class})
public class GrayLoadBalanceAutoConfiguration {
@Bean
public DefaultFilterLoadBalance defaultFilterLoadBalance(List<AbstractServerFilter> strategyEnabledFilterList){
return new DefaultFilterLoadBalance(strategyEnabledFilterList);
}
@Bean
public AbstractServerFilter serverGrayFilter(ContextHandler contextHandler) {
return new ServerGrayFilter(contextHandler);
}
}@AllArgsConstructor
public class DefaultFilterLoadBalance implements FilterLoadBalance {
protected final List<AbstractServerFilter> strategyFilterList;
@Override
public void selectServer(List<ServiceInstance> servers) {
// 应用所有过滤器进行服务选择
for (AbstractServerFilter strategyEnabledFilter : strategyFilterList) {
strategyEnabledFilter.filter(servers);
}
}
}5. 灰度发布调用流程
5.1 完整调用顺序图
5.2 灰度过滤核心流程
6. 配置和使用
6.1 服务灰度配置
在服务的application.yml中配置灰度标识:
spring:
cloud:
nacos:
discovery:
metadata:
gray: true # true表示灰度服务,false表示普通服务6.2 请求灰度标识传递
客户端或网关需要在请求头中添加灰度标识:
GET /api/service HTTP/1.1
Host: example.com
gray: true # 或 false6.3 集成方式
引入依赖:在需要使用灰度功能的服务中引入相应模块
自动配置:框架通过Spring Boot自动配置机制自动生效
服务注册:确保服务正确注册到Nacos,并配置正确的灰度元数据
7. 最佳实践
7.1 灰度策略建议
- 灰度服务命名:在服务部署时明确标识灰度服务
- 灰度监控:为灰度服务添加独立的监控指标,便于对比分析
- 灰度流量控制:通过网关层控制进入灰度服务的流量比例
- 灰度验证:灰度发布后密切关注系统指标和业务数据
7.2 灰度注意事项
- 元数据一致性:确保服务注册时元数据中的灰度标识正确设置
- ThreadLocal清理:确保在请求结束时清理ThreadLocal,防止内存泄漏
- 降级策略:当灰度服务不可用时,确保系统能够正确降级到普通服务
- 调用链追踪:在灰度环境中,确保链路追踪能够正确传递灰度标识
5. 灰度发布平台
5.1 功能模块
- 规则配置:可视化配置灰度规则,支持多种灰度策略
- 灰度监控:实时监控灰度流量和关键指标
- 灰度分析:对比灰度用户和非灰度用户的行为和性能数据
- 灰度管理:支持灰度规则的启用、禁用、修改和删除
- 灰度回滚:一键回滚灰度功能
5.2 灰度流程
- 规则创建:在灰度平台创建灰度规则
- 规则审核:运维或技术负责人审核灰度规则
- 规则发布:将灰度规则发布到规则引擎
- 流量监控:实时监控灰度流量和系统指标
- 数据分析:分析灰度效果和用户反馈
- 灰度调整:根据监控和分析结果调整灰度比例
- 全量发布:验证无误后全量发布新功能
- 灰度下线:功能稳定后下线灰度规则