spring-cloud基于Ribbon的服务间灰度
    
  
      
      
     
    
      
         前言
本次还是灰度系列文章, 基于 Ribbon + nacos 实现
 基础环境搭建
本项目基于聚合项目搭建, 项目结构如下, 其中provider启动2个实例, 通过consumer调用去模拟负载情况。
1 2 3
   | ├── consumer ├── provider ├── pom.xml
   | 
 
 根pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
   | <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>
      <groupId>cn.idea360</groupId>     <artifactId>spring-cloud-demo</artifactId>     <version>0.0.1</version>     <packaging>pom</packaging>
      <properties>         <maven.compiler.source>11</maven.compiler.source>         <maven.compiler.target>11</maven.compiler.target>         <spring-boot.version>2.3.7.RELEASE</spring-boot.version>         <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>         <spring-cloud.version>Hoxton.SR9</spring-cloud.version>     </properties>
      <modules>         <module>provider</module>         <module>gateway</module>         <module>consumer</module>     </modules>
      <dependencies>         <dependency>             <groupId>com.alibaba.cloud</groupId>             <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>         </dependency>         <dependency>             <groupId>com.alibaba.cloud</groupId>             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>         </dependency>     </dependencies>
      <dependencyManagement>         <dependencies>             <dependency>                 <groupId>org.springframework.cloud</groupId>                 <artifactId>spring-cloud-dependencies</artifactId>                 <version>${spring-cloud.version}</version>                 <type>pom</type>                 <scope>import</scope>             </dependency>             <dependency>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-dependencies</artifactId>                 <version>${spring-boot.version}</version>                 <type>pom</type>                 <scope>import</scope>             </dependency>             <dependency>                 <groupId>com.alibaba.cloud</groupId>                 <artifactId>spring-cloud-alibaba-dependencies</artifactId>                 <version>${spring-cloud-alibaba.version}</version>                 <type>pom</type>                 <scope>import</scope>             </dependency>         </dependencies>     </dependencyManagement> </project>
   | 
 
 provider
- application.properties
 
1 2 3 4 5 6 7 8 9
   |  spring.application.name=provider
  server.port=9001
  spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
  spring.cloud.nacos.discovery.metadata.version = gray
 
  | 
 
- 启动主类
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | package cn.idea360.provider;
  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  @EnableDiscoveryClient @SpringBootApplication public class ProviderApplication {
      public static void main(String[] args) {         SpringApplication.run(ProviderApplication.class, args);     }
  }
   | 
 
- 资源提供者
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | package cn.idea360.provider.controller;
  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
 
 
 
 
  @RefreshScope @RestController @RequestMapping("/echo") public class TestController {
      @Autowired     private Environment env;
      @Value("${version:0}")     private String version;     
 
 
      @GetMapping("/port")     public Object port() {         return String.format("port=%s, version=%s", env.getProperty("local.server.port"), version);     } }
   | 
 
- 启动2个实例, 端口分别为9001和9002, 其中9002配置灰度元数据, nacos配置参见 灰度网关
 

 consumer
- application.properties
 
1 2 3 4 5 6 7
   |  spring.application.name=consumer
  server.port=9007
  spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
 
  | 
 
- 启动类
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | package cn.idea360.consumer;
  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate;
  @EnableDiscoveryClient @SpringBootApplication public class ConsumerApplication {
      @LoadBalanced     @Bean     public RestTemplate restTemplate() {         return new RestTemplate();     }     public static void main(String[] args) {         SpringApplication.run(ConsumerApplication.class, args);     }
  }
   | 
 
- 消费者
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | package cn.idea360.consumer.controller;
  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;
 
 
 
 
  @RefreshScope @RestController @RequestMapping("/echo") public class TestController {
      @Autowired     private RestTemplate restTemplate;     
 
 
      @GetMapping("/port")     public Object port() {         return restTemplate.getForObject("http://provider/echo/port", String.class);     } }
   | 
 
- 测试请求发现,默认负载策略为轮询策略
 
1 2 3 4
   | ➜  ~ curl http://localhost:9007/echo/port port=9001, version=2% ➜  ~ curl http://localhost:9007/echo/port port=9002, version=2%
   | 
 
 自定义负载均衡
- 自定义负载
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | package cn.idea360.consumer.ribbon;
  import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
  import java.util.List;
 
 
 
 
  public class GrayRule extends AbstractLoadBalancerRule {
      private final Logger logger = LoggerFactory.getLogger(this.getClass());
      @Override     public void initWithNiwsConfig(IClientConfig iClientConfig) {
      }
      @Override     public Server choose(Object o) {         logger.info("key:{}", o);         ILoadBalancer loadBalancer = getLoadBalancer();         List<Server> allServers = loadBalancer.getAllServers();         logger.info("allServers:{}", allServers.toString());         return allServers.get(0);     } }
   | 
 
- consumer配置文件添加
 
1 2
   |  provider.ribbon.NFLoadBalancerRuleClassName=cn.idea360.consumer.ribbon.GrayRule
 
  | 
 
- 测试
 
1
   | curl http://localhost:9007/echo/port
   | 
 
日志:
1 2
   | 2021-08-28 22:11:14.917  INFO 3620 --- [nio-9007-exec-5] cn.idea360.consumer.ribbon.GrayRule      : key:default 2021-08-28 22:11:14.917  INFO 3620 --- [nio-9007-exec-5] cn.idea360.consumer.ribbon.GrayRule      : allServers:[192.168.124.14:9002, 192.168.124.14:9001]
   | 
 
 nacos+ribbon负载均衡
 consumer配置文件修改
application.properties
1 2 3 4 5 6 7 8 9 10 11 12
   |  spring.application.name=consumer
  server.port=9007
  spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
 
  provider.ribbon.NFLoadBalancerRuleClassName=cn.idea360.consumer.ribbon.GrayRule
  spring.cloud.nacos.discovery.metadata.version = gray
 
  | 
 
 自定义负载均衡策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
   | package cn.idea360.consumer.ribbon;
  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.NamingFactory; 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 org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired;
  import java.security.SecureRandom; import java.util.List; import java.util.stream.Collectors;
 
 
 
 
  public class GrayRule extends AbstractLoadBalancerRule {
      private final Logger logger = LoggerFactory.getLogger(this.getClass());     private static final String VERSION = "version";     @Autowired     private NacosDiscoveryProperties nacosDiscoveryProperties;
      @Override     public void initWithNiwsConfig(IClientConfig iClientConfig) {
      }
      @Override     public Server choose(Object o) {         logger.info("key:{}", o);         String clusterName = this.nacosDiscoveryProperties.getClusterName();         logger.info("clusterName:{}", clusterName);
          String groupName = this.nacosDiscoveryProperties.getGroup();         logger.info("groupName:{}", groupName);
          BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();         String serviceName = baseLoadBalancer.getName();         logger.info("serviceName:{}", serviceName);
          String currentVersion = nacosDiscoveryProperties.getMetadata().get(VERSION);         logger.info("consumer-version:{}", currentVersion);
          try {             NamingService namingService = NamingFactory.createNamingService(nacosDiscoveryProperties.getNacosProperties());             List<Instance> instances = namingService.selectInstances(serviceName, groupName, true);             if (instances.isEmpty()) {                 logger.warn("no instance in service {}", serviceName);                 return null;             }
              List<Instance> targetInstances = instances.stream()                     .filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get(VERSION), currentVersion)                             && StringUtils.equalsIgnoreCase(x.getClusterName(), clusterName)                     ).collect(Collectors.toList());             logger.info("targetInstances:{}", targetInstances);
              if (targetInstances.isEmpty()) {                 return null;             } else {                 SecureRandom random = new SecureRandom();                 int i = random.nextInt(targetInstances.size());                 Instance invokedInstance = targetInstances.get(i);                 logger.info("invokedInstance:{}", invokedInstance.getInstanceId());                 return new NacosServer(invokedInstance);             }         } catch (NacosException e) {             e.printStackTrace();             return null;         }     } }
   | 
 
测试灰度结果,发现所有请求会负载到9002节点
1 2
   | ➜  ~ curl http://localhost:9007/echo/port port=9002, version=2%
   | 
 
provider打印日志
1 2 3 4 5 6 7
   | 2021-11-27 13:54:34.210  INFO 5792 --- [nio-9007-exec-2] cn.idea360.consumer.ribbon.GrayRule      : key:default 2021-11-27 13:54:34.210  INFO 5792 --- [nio-9007-exec-2] cn.idea360.consumer.ribbon.GrayRule      : clusterName:DEFAULT 2021-11-27 13:54:34.210  INFO 5792 --- [nio-9007-exec-2] cn.idea360.consumer.ribbon.GrayRule      : groupName:DEFAULT_GROUP 2021-11-27 13:54:34.210  INFO 5792 --- [nio-9007-exec-2] cn.idea360.consumer.ribbon.GrayRule      : serviceName:provider 2021-11-27 13:54:34.210  INFO 5792 --- [nio-9007-exec-2] cn.idea360.consumer.ribbon.GrayRule      : consumer-version:gray 2021-11-27 13:54:34.216  INFO 5792 --- [nio-9007-exec-2] cn.idea360.consumer.ribbon.GrayRule      : targetInstances:[Instance{instanceId='192.168.124.9#9002#DEFAULT#DEFAULT_GROUP@@provider', ip='192.168.124.9', port=9002, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='DEFAULT', serviceName='DEFAULT_GROUP@@provider', metadata={preserved.register.source=SPRING_CLOUD, version=gray}}] 2021-11-27 13:54:34.217  INFO 5792 --- [nio-9007-exec-2] cn.idea360.consumer.ribbon.GrayRule      : invokedInstance:192.168.124.9#9002#DEFAULT#DEFAULT_GROUP@@provider
   | 
 
 方案总结
服务间调用灰度方案(Consumer -> Provider)
- P1、P2、C1、C2服务注册在nacos,并分别在nacos元数据配置版本号V1和V2
 
- C用RestTemplate通过服务发现调用P
 
- 自定义负载均衡策略
 
- C读取自己元数据中的版本号, 代表自己想访问哪个版本的P(负载均衡策略)
 
- 获取所有的P实例, 并过滤出符合自己版本的实例(负载均衡策略)
 
- 最终的结果是C2调用P2, C1调用P1
 
 最后
本文到此结束,感谢阅读。如果您觉得不错,请关注公众号【当我遇上你】,您的支持是我写作的最大动力。