当前位置: 首页>后端>正文

微服务下的金丝雀发布

一、书写背景:

最近跟一些朋友在讨论技术,他们都提到了微服务下金丝雀发布没有什么头绪,但微服务下金丝雀发布又是现实工作中不过避免的事实。刚好最近不是特别忙,可以抽出一点时间写点东西(微服务下金丝雀发布)。考虑到看文章的读者能够对金丝雀发布有所理解(大概思路:发布过程中,先发一台或者一小部分比例的机器作为金丝雀,用于流量验证。如果金丝雀验证通过则把剩余机器全部发掉。如果金丝雀验证失败,则直接回退金丝雀)

微服务下的金丝雀发布,第1张

二、整理思路:

微服务下的金丝雀发布,第2张
最终目的:服务调用时相同version的节点进行通信

1.在每个新版本包中设计相同的标签,此标签可以是基于服务器注册中心的元数据(metadata),也可以是基于配置中心标识配置。

2.自定义loadBalancer,让其在Balancer时能够识别出当前服务的标签与被调用服务的标签是否相同,若相关则选择其他中一个Instance进行路由,若没有到相同的标签,在根据新的算法选择其他一个Instance进行路由

3.让自定义的loadBalancer生效

三、具体实现:

1.pom导入nacos依赖

<!--根节点-->

? ? <parent>

? ? ? ? <groupId>org.springframework.boot</groupId>

? ? ? ? <artifactId>spring-boot-starter-parent</artifactId>

? ? ? ? <version>2.3.12.RELEASE</version>

? ? ? ? <relativePath/>

? ? </parent>

<!--springcloud 子项目nacos服务发现与注册依赖-->

? ? <dependency>

? ? ? ? <groupId>com.alibaba.cloud</groupId>

? ? ? ? <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

? ? </dependency>

<!--多父继承 -->

? ? <dependencyManagement>

? ? ? ? <dependencies>

? ? ? ? ? ? <!--spring-cloud-dependencies-->

? ? ? ? ? ? <dependency>

? ? ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>

? ? ? ? ? ? ? ? <artifactId>spring-cloud-dependencies</artifactId>

? ? ? ? ? ? ? ? <version>Hoxton.SR9</version>

? ? ? ? ? ? ? ? <type>pom</type>

? ? ? ? ? ? ? ? <scope>import</scope>

? ? ? ? ? ? </dependency>

? ? ? ? ? ? <!--alibaba nacos -->

? ? ? ? ? ? <dependency>

? ? ? ? ? ? ? ? <groupId>com.alibaba.cloud</groupId>

? ? ? ? ? ? ? ? <artifactId>spring-cloud-alibaba-dependencies</artifactId>

? ? ? ? ? ? ? ? <version>2.2.2.RELEASE</version>

? ? ? ? ? ? ? ? <type>pom</type>

? ? ? ? ? ? ? ? <scope>import</scope>

? ? ? ? ? ? </dependency>

? ? ? ? </dependencies>

? ? </dependencyManagement>

2.自定义loadbanlance

//第一步:定义常量类,封装灰度标签,灰度标签值可以通过第三方配置中心,如nacos、apollo、config等,以下只是做实例,故直接写在了常量类

package com.charlie.cloudportal.common.constants;

public class GrayConstant {

????public static final String GRAY_TAG ="version";

}

//第二步:实现loadbanlance,通过loadbalancer源码查看,其顶层为IRule接口,但考虑到复用IRule其他实现类的一些功能,故推荐实现其AbstractLoadBalancerRule 抽象类,具体如下:

package com.charlie.cloudportal.config.rule;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;

import com.alibaba.cloud.nacos.NacosServiceManager;

import com.alibaba.cloud.nacos.ribbon.ExtendBalancer;

import com.alibaba.cloud.nacos.ribbon.NacosServer;

import com.alibaba.nacos.api.naming.NamingService;

import com.alibaba.nacos.api.naming.pojo.Instance;

import com.charlie.cloudportal.common.constants.GrayConstant;

import com.netflix.client.config.IClientConfig;

import com.netflix.loadbalancer.AbstractLoadBalancerRule;

import com.netflix.loadbalancer.DynamicServerListLoadBalancer;

import com.netflix.loadbalancer.Server;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.util.CollectionUtils;

import java.util.*;

/**

* @Author: charlie

* @CreateTime: 2022/3/13

* Description: 金丝雀版本权重负载均衡策略--优先选择相同版本节点进行通信

*/

@Slf4j

public class CanaryBalancerRule extends AbstractLoadBalancerRule {

? ? @Autowired

? ? private NacosDiscoveryProperties nacosDiscoveryProperties;

? ? @Autowired

? ? private NacosServiceManager nacosServiceManager;

? ? public CanaryBalancerRule() {

? ? }

? ? @Override

? ? public void initWithNiwsConfig(IClientConfig iClientConfig) {

? ? }

? ? @Override

? ? public Server choose(Object key) {

? ? ? ? try {

? ? ? ? ? ? //获取集群名称

? ? ? ? ? ? String clusterName = this.nacosDiscoveryProperties.getClusterName();

? ? ? ? ? ? //获取分组

? ? ? ? ? ? String group = this.nacosDiscoveryProperties.getGroup();

? ? ? ? ? ? //获取当前服务的名称

? ? ? ? ? ? String currServerName = this.nacosDiscoveryProperties.getService();

? ? ? ? ? ? //获取当前服务的metaData数据

? ? ? ? ? ? Map<String,String> ribbonAttributes = this.nacosDiscoveryProperties.getMetadata();

? ? ? ? ? ? //获取被调用的服务名

? ? ? ? ? ? DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer();

? ? ? ? ? ? String serviceName = loadBalancer.getName();

? ? ? ? ? ? log.info("[ribbon负载均衡策略] 当前服务名: {}", currServerName);

? ? ? ? ? ? //获取服务发现客户端

? ? ? ? ? ? NamingService namingService = this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties());

? ? ? ? ? ? // 获取被调用的服务实例列表

? ? ? ? ? ? List<Instance> allInstances = namingService.selectInstances(serviceName, group, true);

? ? ? ? ? ? log.info("[ribbon负载均衡策略] 可用的服务实例: {}", allInstances);

? ? ? ? ? ? // 可供选择的灰度服务

? ? ? ? ? ? List<Instance> grayInstances = new ArrayList<>();

? ? ? ? ? ? // 非灰服务

? ? ? ? ? ? List<Instance> noneGrayInstances = new ArrayList<>();

? ? ? ? ? ? Instance toBeChooseInstance;

? ? ? ? ? ? if (CollectionUtils.isEmpty(allInstances)) {

? ? ? ? ? ? ? ? log.warn("no instance in service {}", serviceName);

? ? ? ? ? ? ? ? return null;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? //List<Instance> instancesToChoose = allInstances;

? ? ? ? ? ? ? ? if (StringUtils.isNotBlank(clusterName)) {

? ? ? ? ? ? ? ? ? ? for (Instance instance : allInstances) {

? ? ? ? ? ? ? ? ? ? ? ? Map<String, String> metadata = instance.getMetadata();? ? ? ? ? ? ? ? ? ? ? ? if(ribbonAttributes.get(GrayConstant.GRAY_TAG).trim().toLowerCase().equals(metadata.get(GrayConstant.GRAY_TAG).trim().toLowerCase() )){

? ? ? ? ? ? ? ? ? ? ? ? ? ? log.info("当前服务:{},灰度tag为:{} ----- 被调服务:{} 进行灰度匹配,已匹配灰度服务:{},灰度tag为:{}",currServerName,ribbonAttributes.get(GrayConstant.GRAY_TAG), serviceName,instance, this.nacosDiscoveryProperties.getMetadata().get(GrayConstant.GRAY_TAG));

? ? ? ? ? ? ? ? ? ? ? ? ? ? grayInstances.add(instance);

? ? ? ? ? ? ? ? ? ? ? ? } else if (!StringUtils.isBlank(metadata.get(GrayConstant.GRAY_TAG))) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? log.info("服务:{} 进行灰度匹配,未匹配灰度服务:{},metadata为:{}", serviceName,instance,metadata);

? ? ? ? ? ? ? ? ? ? ? ? ? ? log.info("当前服务:{},灰度tag为:{} ----- 被调服务:{} 进行灰度匹配,未匹配灰度服务:{},灰度tag为:{}",currServerName,ribbonAttributes.get(GrayConstant.GRAY_TAG), serviceName,instance, this.nacosDiscoveryProperties.getMetadata().get(GrayConstant.GRAY_TAG));

? ? ? ? ? ? ? ? ? ? ? ? ? ? // 非灰度服务

? ? ? ? ? ? ? ? ? ? ? ? ? ? noneGrayInstances.add(instance);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? log.info("[ribbon负载均衡策略] 灰度服务: {}, 非灰服务:{}", grayInstances, noneGrayInstances);

? ? ? ? ? ? ? ? // 如果灰度服务不为空, 则走灰度服务

? ? ? ? ? ? ? ? if (grayInstances != null && grayInstances.size() > 0) {

? ? ? ? ? ? ? ? ? ? // 走灰度服务 -- 从本集群中按照权重随机选择一个服务实例

? ? ? ? ? ? ? ? ? ? toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(grayInstances);

? ? ? ? ? ? ? ? ? ? log.info("[ribbon负载均衡策略] 灰度规则匹配成功, 匹配的灰度服务是: {}", toBeChooseInstance);

? ? ? ? ? ? ? ? ? ? return new NacosServer(toBeChooseInstance);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? // 灰度服务为空, 走非断灰的服务

? ? ? ? ? ? ? ? if (noneGrayInstances != null && noneGrayInstances.size() > 0) {

? ? ? ? ? ? ? ? ? ? // 走非灰服务 -- 从本集群中按照权重随机选择一个服务实例

? ? ? ? ? ? ? ? ? ? toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(noneGrayInstances);

? ? ? ? ? ? ? ? ? ? // toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(noneGrayInstances);

? ? ? ? ? ? ? ? ? ? log.info("[ribbon负载均衡策略] 不走灰度, 匹配的非灰度服务是: {}", toBeChooseInstance);

? ? ? ? ? ? ? ? ? ? return new NacosServer(toBeChooseInstance);

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? log.info("未找到可匹配服务,实际服务:{}", allInstances);

? ? ? ? ? ? ? ? ? ? //按nacos权重获取。这个是NacosRule的代码copy 过来 没有自己实现权重随机。这个权重是nacos控制台服务的权重设置

? ? ? ? ? ? ? ? ? ? // 如果业务上有自己特殊的业务。可以自己定制规则,黑白名单,用户是否是灰度用户,测试账号。等等一些自定义设置

? ? ? ? ? ? ? ? ? ? toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(allInstances);

? ? ? ? ? ? ? ? ? ? // toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(allInstances);

? ? ? ? ? ? ? ? ? ? log.info("[ribbon负载均衡策略] 未找到可匹配服务, 随机选择一个: {}", toBeChooseInstance);

? ? ? ? ? ? ? ? ? ? return new NacosServer(toBeChooseInstance);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? } catch (Exception var10) {

? ? ? ? ? ? log.warn("NacosRule error", var10);

? ? ? ? ? ? return null;

? ? ? ? }

? ? }

}

3.修改默认负载均衡策略

//第一步:创建配置类,修改默认负载均衡策略

package com.charlie.cloudportal.config;

import com.charlie.cloudportal.config.rule.CanaryBalancerRule;

import com.netflix.loadbalancer.IRule;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

/**

* @Author: charlie

* @CreateTime: 2022/3/13

* Description: 全局配置类

*/

@Configuration

public class RibbonConfig {

? ? /**

? ? * 全局配置

? ? * 指定负载均衡策略

? ? * @return

? ? */

? ? @Bean

? ? public IRule canaryBalancerRule() {

? ? ? ? return new CanaryBalancerRule();

? ? }

}

//第二步:在bootstrap.yml文件中开启ribbon饥饿加载,解决第一次调用慢的问题

#ribbon默认是懒加载,为了解决第一次调用慢的问题(项目启动时不会创建,只有在发起调用的时候才会创建客户端),可以开启饥饿加载

ribbon:

#?开启ribbon饥饿加载: true开始;false关闭

? eager-load:

enabled:true

4. 配置版本号

在bootstrap.yml文件中配置版本号

spring:

? cloud:

? ? nacos:

? ? ? discovery:

? ? ? ? metadata:

? ? ? ? ? ? version: 1.0.0

? ? ? ? ? ? active: ${spring.profiles.active}

四、效果展示

1.nacos注册的服务信息如下:

portal在nacos注册的服务信息如下:

微服务下的金丝雀发布,第3张

consumer在nacos注册的服务信息如下:

微服务下的金丝雀发布,第4张

2.访问portal

127.0.0.1:18020/portal-provider/userLogin?username=1234,通过控制台信息证明,version=1.0.0的服务进行了通信

微服务下的金丝雀发布,第5张
微服务下的金丝雀发布,第6张
微服务下的金丝雀发布,第7张
微服务下的金丝雀发布,第8张

127.0.0.1:18021/portal-provider/userLogin?username=1234,通过控制台信息证明,version=1.0.1的服务进行了通信

微服务下的金丝雀发布,第9张
微服务下的金丝雀发布,第10张
微服务下的金丝雀发布,第11张
微服务下的金丝雀发布,第12张

以上的构思仅提供一个思路,期望对看到的您有所启发,同时上述代码是经过本人运行过的!


https://www.xamrdz.com/backend/3q91931362.html

相关文章: