需求
新功能要上线了 , order-center 存在二个版本 V1(老版本) V2(新版本),product-center也存在二个版本V1(老版本) V2新版本 现在需要做到的是order-center(V1)---->product-center(v1),order-center(V2)---->product-center(v2)。
v2版本是小面积部署的,用来测试用户对新版本功能的。若用户完全接受了v2。我们就可以把V1版本卸载完全部署V2版本。
如下调用关系
改造
Spring Cloud Alibaba - 11 Ribbon 自定义负载均衡策略(同集群优先权重负载均衡算法) 在这个基础上实现该功能
自定义规则
package com.artisan.customrules; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; /** * @author 小工匠 * @version 1.0 * @description: 金丝雀版本权重负载均衡策略 * @date 2022/2/3 13:43 * @mark: show me the code , change the world */ @Slf4j public class SameClusterPriorityWithVersionRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } @Override public Server choose(Object key) { try { //获取本地所部署集群的名称 NJ-CLUSTER String localClusterName = discoveryProperties.getClusterName(); //去nacos上获取和本地 相同集群 相同版本的所有实例信息 List<Instance> theSameClusterNameAndTheSameVersionInstList = getTheSameClusterAndTheSameVersionInstances(discoveryProperties); //声明被调用的实例 Instance toBeChooseInstance; //判断同集群同版本号的微服务实例是否为空 if(theSameClusterNameAndTheSameVersionInstList.isEmpty()) { //跨集群调用相同的版本 toBeChooseInstance = crossClusterAndTheSameVersionInovke(discoveryProperties); }else { //具有同集群 同版本号的实例 toBeChooseInstance = ArtisanWeightedBalancer.chooseInstanceByRandomWeight(theSameClusterNameAndTheSameVersionInstList); log.info("同集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}", localClusterName,toBeChooseInstance.getClusterName(),discoveryProperties.getMetadata().get("current-version"), toBeChooseInstance.getMetadata().get("current-version"),toBeChooseInstance.getIp(),toBeChooseInstance.getPort()); } return new NacosServer(toBeChooseInstance); } catch (NacosException e) { log.error("同集群优先权重负载均衡算法选择异常:{}",e); return null; } } /** * 方法实现说明:获取相同集群下,相同版本的 所有实例 * @author:smlz * @param discoveryProperties nacos的配置 * @return: List<Instance> * @exception: NacosException */ private List<Instance> getTheSameClusterAndTheSameVersionInstances(NacosDiscoveryProperties discoveryProperties) throws NacosException { //当前的集群的名称 String currentClusterName = discoveryProperties.getClusterName(); //当前的版本号 String currentVersion = discoveryProperties.getMetadata().get("current-version"); //获取所有实例的信息(包括不同集群的,不同版本号的) List<Instance> allInstance = getAllInstances(discoveryProperties); List<Instance> theSameClusterNameAndTheSameVersionInstList = new ArrayList<>(); //过滤相同集群 同版本号的实例 for(Instance instance : allInstance) { if(StringUtils.endsWithIgnoreCase(instance.getClusterName(),currentClusterName)&& StringUtils.endsWithIgnoreCase(instance.getMetadata().get("current-version"),currentVersion)) { theSameClusterNameAndTheSameVersionInstList.add(instance); } } return theSameClusterNameAndTheSameVersionInstList; } /** * 方法实现说明:获取被调用服务的所有实例 * @author:smlz * @param discoveryProperties nacos的配置 * @return: List<Instance> * @exception: NacosException */ private List<Instance> getAllInstances(NacosDiscoveryProperties discoveryProperties) throws NacosException { //第1步:获取一个负载均衡对象 BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer(); //第2步:获取当前调用的微服务的名称 String invokedSerivceName = baseLoadBalancer.getName(); //第3步:获取nacos clinet的服务注册发现组件的api NamingService namingService = discoveryProperties.namingServiceInstance(); //第4步:获取所有的服务实例 List<Instance> allInstance = namingService.getAllInstances(invokedSerivceName); return allInstance; } /** * 方法实现说明:跨集群环境下 相同版本的 * @author:smlz * @param discoveryProperties * @return: List<Instance> * @exception: NacosException */ private List<Instance> getCrossClusterAndTheSameVersionInstList(NacosDiscoveryProperties discoveryProperties) throws NacosException { //版本号 String currentVersion = discoveryProperties.getMetadata().get("current-version"); //被调用的所有实例 List<Instance> allInstance = getAllInstances(discoveryProperties); List<Instance> crossClusterAndTheSameVersionInstList = new ArrayList<>(); //过滤相同版本 for(Instance instance : allInstance) { if(StringUtils.endsWithIgnoreCase(instance.getMetadata().get("current-version"),currentVersion)) { crossClusterAndTheSameVersionInstList.add(instance); } } return crossClusterAndTheSameVersionInstList; } private Instance crossClusterAndTheSameVersionInovke(NacosDiscoveryProperties discoveryProperties) throws NacosException { //获取所有集群下相同版本的实例信息 List<Instance> crossClusterAndTheSameVersionInstList = getCrossClusterAndTheSameVersionInstList(discoveryProperties); //当前微服务的版本号 String currentVersion = discoveryProperties.getMetadata().get("current-version"); //当前微服务的集群名称 String currentClusterName = discoveryProperties.getClusterName(); //声明被调用的实例 Instance toBeChooseInstance = null ; //没有对应相同版本的实例 if(crossClusterAndTheSameVersionInstList.isEmpty()) { log.info("跨集群调用找不到对应合适的版本当前版本为:currentVersion:{}",currentVersion); throw new RuntimeException("找不到相同版本的微服务实例"); }else { toBeChooseInstance = ArtisanWeightedBalancer.chooseInstanceByRandomWeight(crossClusterAndTheSameVersionInstList); log.info("跨集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}", currentClusterName,toBeChooseInstance.getClusterName(),discoveryProperties.getMetadata().get("current-version"), toBeChooseInstance.getMetadata().get("current-version"),toBeChooseInstance.getIp(),toBeChooseInstance.getPort()); } return toBeChooseInstance; } }
全局规则配置
@Configuration public class GlobalRibbonConfig { @Bean public IRule globalConfig() { // 根据权重的规则 // return new ArtisanWeightedRule(); // 同集群优先调用规则 // return new SameClusterPriorityRule(); // 金丝雀版本权重负载均衡策略 return new SameClusterPriorityWithVersionRule(); } }
配置文件
ORDER -------- BeiJingCluster V1 PRODUCT -------- BeiJingCluster V1 PRODUCT -------- BeiJingCluster V2 PRODUCT -------- GuangDongCluster V1 PRODUCT -------- GuangDongCluster V2
artisan-cloud-customcfg-ribbon-order工程中Nacos的配置如下:
spring: cloud: nacos: discovery: server-addr: 1.117.97.88:8848 cluster-name: BeiJingCluster metadata: current-version: V1 application: name: artisan-order-center server: port: 8080
服务启动后
artisan-cloud-customcfg-ribbon-product工程中Nacos的配置如下:
【BeiJingCluster V1】
spring: cloud: nacos: discovery: server-addr: 1.117.97.88:8848 cluster-name: BeiJingCluster metadata: current-version: V1 application: name: artisan-product-center server: port: 7777
【BeiJingCluster V2】
spring: cloud: nacos: discovery: server-addr: 1.117.97.88:8848 cluster-name: BeiJingCluster metadata: current-version: V2 application: name: artisan-product-center server: port: 7778
【GuangDongCluster V1】
spring: cloud: nacos: discovery: server-addr: 1.117.97.88:8848 cluster-name: GuangDongCluster metadata: current-version: V1 application: name: artisan-product-center server: port: 7779
【GuangDongCluster V2】
spring: cloud: nacos: discovery: server-addr: 1.117.97.88:8848 cluster-name: GuangDongCluster metadata: current-version: V2 application: name: artisan-product-center server: port: 7780
验证
访问Order提供的接口,
观察访问日志
2022-02-03 19:06:32.791 INFO 1856 --- [nio-8080-exec-1] c.a.c.SameClusterPriorityWithVe
再看看Product 7777
测试结果来看order调用product的时候 优先会调用同集群同版本的product提供的服务。
紧接着,我们把同集群的 同版本的product-center下线,那们就会发生跨集群调用相同的版本
再次调用, 观察日志
2022-02-03 19:10:24.754 INFO 1856 --- [nio-8080-exec-4] c.a.c.SameClusterPriorityWithVe
说明了当当前集群不存在相同版本的服务时,会跨集群调用相同的版本 。
当我们把Order 也搞个V2 版本
再次调用
观察日志
源码
https://github.com/yangshangwei/SpringCloudAlibabMaster/tree/master