Spring Cloud - Ribbon在Zuul上实现灰度和版本测试

本文依赖ribbon实现在Spring Cloud中的灰度测试。

POM引入

Zuul及下游服务中均引入包

1
2
3
4
5
6
<!-- 实现灰度测试关键包 -->
<dependency>
<groupId>io.jmnarloch</groupId>
<artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
<version>2.1.0</version>
</dependency>

Zuul服务开发

1
2
3
4
5
6
7
8
9
10
11
12
13
private void fixGray(UserInfo userInfo) {
// 配置转发渠道,例如用户id
if (isGrayer(userInfo.getUserId())) {
RibbonFilterContextHolder.clearCurrentContext();
//灰度标识
RibbonFilterContextHolder.getCurrentContext().add("prod", "2");
RibbonFilterContextHolder.getCurrentContext().add("prod", "1.0");
} else {
RibbonFilterContextHolder.clearCurrentContext();
RibbonFilterContextHolder.getCurrentContext().add("prod", "1");
RibbonFilterContextHolder.getCurrentContext().add("prod", "1.1");
}
}

下游服务开发

添加版本拦截器

1
2
3
4
5
6
7
8
9
10
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

@Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(new VersionInterceptor());
}

}

实现拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class VersionInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//清除现有数据,防止干扰
RibbonFilterContextHolder.clearCurrentContext();
String prod = request.getHeader("prod");
String version = request.getHeader("version");
if (!StringUtils.isEmpty(prod)&&!StringUtils.isEmpty(version)) {
RibbonFilterContextHolder.getCurrentContext().add("prod", prod);
RibbonFilterContextHolder.getCurrentContext().add("version", version);
}

return true;
}
}

下游服务配置

1
2
3
4
5
6
7
8
9
10
eureka:
instance:
preferIpAddress: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
metadataMap: #元信息
prod: 2 #灰度标识,1生产服务,2为灰度服务
version: 1.1 #服务版本标识
client:
registerWithEureka: true
fetchRegistry: true

注意事项

  • 通过以上配置,启动各服务,可以实现灰度测试和版本测试
  • 在网关依据灰度和版本请求标识,Ribbon利用各服务的元信息进行匹配,以实现过滤和负载
  • 服务中必须配置相应的请求标识,否则该请求无法负载,将会报错
  • 关闭组件,ribbon.filter.metadata.enabled=false #默认true

源码及原理分析

  • 元信息筛选

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class MetadataAwarePredicate extends DiscoveryEnabledPredicate {

    /**
    * {@inheritDoc}
    */
    @Override
    protected boolean apply(DiscoveryEnabledServer server) {

    //当前请求上下文
    final RibbonFilterContext context = RibbonFilterContextHolder.getCurrentContext();
    //当前请求属性
    final Set<Map.Entry<String, String>> attributes = Collections.unmodifiableSet(context.getAttributes().entrySet());
    //当前服务元信息
    final Map<String, String> metadata = server.getInstanceInfo().getMetadata();
    //服务元信息是否完全包含请求属性
    return metadata.entrySet().containsAll(attributes);
    }
    }
  • 注册实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Configuration
    @ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
    @AutoConfigureBefore(RibbonClientConfiguration.class)
    @ConditionalOnProperty(value = "ribbon.filter.metadata.enabled", matchIfMissing = true)
    public class RibbonDiscoveryRuleAutoConfiguration {

    //在Spring application context注册DiscoveryEnabledRule
    @Bean
    @ConditionalOnMissingBean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public DiscoveryEnabledRule metadataAwareRule() {
    //使用自定义元信息过滤
    return new MetadataAwareRule();
    }
    }
  • 配置请求信息

    RibbonFilterContextHolder.getCurrentContext().add("version", "1.1").add("variant", "A");

    在zuul中配置,以便调取下游

    在下游拦截器中配置,以便当前服务继续调用下游服务

    也可以在RestTemplateClientHttpRequestInterceptor中配置

------ 本文结束------

本文标题:Spring Cloud - Ribbon在Zuul上实现灰度和版本测试

文章作者:Perkins

发布时间:2019年04月23日

原始链接:https://perkins4j2.github.io/posts/25628/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。