前言
灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度 —— 百度百科
📒 下面把上面这段表述抽象成程序设计模型:
比如现在有2个服务,user服务和order服务,user服务通过在注册中心拉取order服务的地址来消费order服务,灰度发布其实就是让v1版本的user去消费v1版本的order,让v2版本的user去消费v2版本的order。
Ribbon是一个Netflix公司开发的的负载均衡组件,通过自定义实现它的负载均衡策略,可以实现我们的需求。
1.配置负载均衡策略
Nacos中有实现一个优先访问同一ClusterName的Service的负载均衡策略NacosRule,我们可以参考其源码实现。
先上GrayReleasedRule的代码:
public class GrayReleasedRule extends AbstractLoadBalancerRule {
private static final Logger LOGGER = LoggerFactory.getLogger(GrayReleasedRule.class);
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public Server choose(Object key) {
try {
String version = this.nacosDiscoveryProperties.getMetadata().get("version");
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties
.namingServiceInstance();
List<Instance> instances = namingService.selectInstances(name, true);
if (CollectionUtils.isEmpty(instances)) {
LOGGER.warn("no instance in service {}", name);
return null;
}
List<Instance> instancesToChoose = instances;
if (StringUtils.isNotBlank(version)) {
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(version,
instance.getMetadata().get("version")))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
}
else {
LOGGER.warn(
"A version-service scall occurs,name = {}, version = {}, instance = {}",
name, version, instances);
}
}
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
return new NacosServer(instance);
}
catch (Exception e) {
LOGGER.warn("GrayReleasedRule error", e);
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
首先我们需要继承一个AbstractLoadBalancerRule 抽象类,它实现了IRule 接口,我们需要实现它的choose(Object key)方法,关键部分的代码我把它摘出来:
if (StringUtils.isNotBlank(version)) {
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(version,
instance.getMetadata().get("version")))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
}
else {
LOGGER.warn(
"A version-service scall occurs,name = {}, version = {}, instance = {}",
name, version, instances);
}
}
我选用的注册中心是Nacos,整合到SpringCloud使用,所以我在 在yml配置文件上的元数据metadata 字段中配上version 字段
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8888
metadata:
version: v1
首先获取到我们配置的版本号version
String version = this.nacosDiscoveryProperties.getMetadata().get("version");
判断从注册中心获取的version 不为空的时候,判断是否和当前服务的版本号相同,相同的话就去访问同一版本号的服务。
总结,只需要建立多套服务实例,配置不同的版本号,选择目标用户群体,让请求分散到不同版本号的入口服务上,就能实现不同版本服务的隔离。
2.指定负载均衡策略
通过SpringBean生成策略返回一个IRule接口类型的实例交给SpringContainer管理,覆盖掉Ribbon的默认生成策略
@Configuration
public class RibbonGrayReleasedConfig {
public IRule ribbonRule() {
return new GrayReleasedRule();
}
}
补充:Ribbon负载均衡策略
1.RandomRule: 随机选择一个Server。 2.RetryRule: 对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server。 3.RoundRobinRule: 轮询选择, 轮询index,选择index对应位置的Server。 4.AvailabilityFilteringRule: 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。 5.BestAvailableRule: 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。 6.WeightedResponseTimeRule: 根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。 7.ZoneAvoidanceRule: 默认的负载均衡策略,即复合判断Server所在区域的性能和Server的可用性选择Server,在没有区域的环境下,类似于轮询(RandomRule) 8.NacosRule(Nacos的自定义实现): 同集群优先调用
有兴趣的可以自行测试
|