大家好,我是一安~
导读:在上一篇文章中我们讲解了如何整合sky-walking
实现链路追踪,今天继续讲解如何通过网关和配置中心实现灰度发布。
简介
灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing
,即让一部分用户继续用产品特性A
,一部分用户开始用产品特性B
,如果用户对B
没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
灰度发布开始到结束期间的这一段时间,称为灰度期。灰度发布能及早获得用户的意见反馈,完善产品功能,提升产品质量,让用户参与产品测试,加强与用户互动,降低产品升级所影响的用户范围。
正文
其实之前讲解配置隔离的时候,也可以通过
group
或namespace
实现服务注册的隔离,这种方式不再做介绍。
环境搭建测试
首先搭建两个web
服务模拟生产和灰度环境(以用户模块为例,小编这里直接复制了一份),分别注册到nacos
中,注意这里服务实例名称要一致:
生产环境配置:
spring:
application:
name: account-service
mvc:
pathmatch:
matching-strategy: ant_path_matcher
cloud:
loadbalancer:
nacos:
enabled: true
ribbon:
enabled: false
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
group: DEFAULT_GROUP
namespace: public
metadata:
# 0-正式环境 1-测试环境
env: 0
灰度环境配置:
spring:
application:
name: account-service
mvc:
pathmatch:
matching-strategy: ant_path_matcher
cloud:
loadbalancer:
nacos:
enabled: true
ribbon:
enabled: false
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
group: DEFAULT_GROUP
namespace: public
metadata:
# 0-正式环境 1-测试环境
env: 1
Nacos
中查看详情:修改用户服务查询接口日志输出:
@SysLog(desc = "查询用户")
@ApiOperation("查询用户")
@GetMapping("/account/getByCode/{accountCode}")
@SentinelResource(value = "getByCode")
public Result<Account> getByCode(@PathVariable(value = "accountCode") String accountCode){
log.info("get account detail,accountCode is :{}",accountCode);
Account account = accountService.selectByCode(accountCode);
return Result.ok(account).message("正式环境");
}
@SysLog(desc = "查询用户")
@ApiOperation("查询用户")
@GetMapping("/account/getByCode/{accountCode}")
@SentinelResource(value = "getByCode")
public Result<Account> getByCode(@PathVariable(value = "accountCode") String accountCode){
log.info("get account detail,accountCode is :{}",accountCode);
Account account = accountService.selectByCode(accountCode);
return Result.ok(account).message("测试环境");
}
测试(默认轮询)
网关优化
新增配置:
# 0-正式环境 1-测试环境
env: 0
解析配置:
@Data
@Configuration
@RefreshScope
public class EnvProperties {
@Value("${env:0}")
private String env;
}
自定义负载均衡策略(方式一):
@Slf4j
public class EnvGrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
@Autowired
private EnvProperties envProperties;
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
private final AtomicInteger position;
public EnvGrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
this(serviceInstanceListSupplierProvider,serviceId,new Random().nextInt(1000));
}
public EnvGrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(serviceInstances,request));
}
private Response<ServiceInstance> processInstanceResponse(List<ServiceInstance> instances, Request request) {
if (instances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
return new EmptyResponse();
} else {
DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
RequestData clientRequest = (RequestData) requestContext.getClientRequest();
//通过请求头获取环境参数
// HttpHeaders headers = clientRequest.getHeaders();
// String env = headers.getFirst("env");
//通过配置文件获取环境参数
String env = envProperties.getEnv();
log.info("request env : {}",env );
if(StringUtils.isEmpty(env)){
return processRibbonInstanceResponse(instances);
}
// filter service instances
List<ServiceInstance> serviceInstances = instances.stream()
.filter(instance -> env.equalsIgnoreCase(instance.getMetadata().get("env")))
.collect(Collectors.toList());
if(serviceInstances.size() > 0){
return processRibbonInstanceResponse(serviceInstances);
}else{
return processRibbonInstanceResponse(instances);
}
}
}
/**
* 负载均衡器
* 参考 org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer#getInstanceResponse
* @author javadaily
*/
private Response<ServiceInstance> processRibbonInstanceResponse(List<ServiceInstance> instances) {
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
编写配置类:
public class EnvLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> envGrayLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new EnvGrayLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
启动类新增注解:
@LoadBalancerClient(value = "account-service", configuration = EnvLoadBalancerConfiguration.class)
自定义负载均衡策略(方式二):
@Slf4j
public class EnvServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {
@Autowired
private EnvProperties envProperties;
public EnvServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
super(delegate);
}
@Override
public Flux<List<ServiceInstance>> get() {
return delegate.get();
}
@Override
public Flux<List<ServiceInstance>> get(Request request) {
// return delegate.get(request).map(instances -> filteredByVersion(instances,getEnv(request.getContext())));
return delegate.get(request).map(instances -> filteredByVersion(instances,envProperties.getEnv()));
}
/**
* filter instance by requestVersion
*/
private List<ServiceInstance> filteredByVersion(List<ServiceInstance> instances, String env) {
log.info("DelegatingServiceInstanceListSupplier version is {}",env);
if(StringUtils.isEmpty(env)){
return instances;
}
List<ServiceInstance> filteredInstances = instances.stream()
.filter(instance -> env.equalsIgnoreCase(instance.getMetadata().getOrDefault("env","")))
.collect(Collectors.toList());
if (filteredInstances.size() > 0) {
return filteredInstances;
}
return instances;
}
private String getEnv(Object requestContext) {
if (requestContext == null) {
return null;
}
String env = null;
if (requestContext instanceof RequestDataContext) {
env = getVersionFromHeader((RequestDataContext) requestContext);
}
return env;
}
/**
* get version from header
*/
private String getVersionFromHeader(RequestDataContext context) {
if (context.getClientRequest() != null) {
HttpHeaders headers = context.getClientRequest().getHeaders();
if (headers != null) {
return headers.getFirst("env");
}
}
return null;
}
}
编写配置类:
public class EnvLoadBalancerConfiguration2 {
@Bean
ServiceInstanceListSupplier serviceInstanceListSupplier(ConfigurableApplicationContext context) {
ServiceInstanceListSupplier delegate = ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withCaching()
.build(context);
return new EnvServiceInstanceListSupplier(delegate);
}
}
启动类新增注解:
@LoadBalancerClient(value = "account-service", configuration = EnvLoadBalancerConfiguration2.class)
测试:
至此我们已经通过自定义负载均衡策略实现灰度发布。
如果这篇文章对你有所帮助,或者有所启发的话,帮忙 分享、收藏、点赞、在看,你的支持就是我坚持下去的最大动力!
深入剖析Spring Boot 的SPI机制,提升程序的可扩展性
原文始发于微信公众号(一安未来):SpringCloud Alibaba微服务实战之灰度发布
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145044.html