SpringCloud - Gateway (2) 应用案例 - 根据当前运行环境访问不同环境的服务实例

目标:根据当前运行时环境,调用不同环境的服务实例,实现环境的隔离。比如本地开发环境,服务间调用时,只会调用本地环境中的服务,而不是调用测试环境中的服务。或者做一个保底处理,当本地开发环境进行代码调试时,优先获取本地环境中的服务节点,若本地没有运行该服务,则调用测试环境中的该服务。这样可以便利我们进行代码自测,只需要将需要测试的服务在本地启动,其余的服务依旧调用测试环境。

实现步骤

  1. 在测试环境的 nacos 配置文件中添加 test.server.ips 配置项,指定测试环境的服务地址
  2. 关闭网关默认的 ribbon 负载均衡器,注册我们自己实习的负载均衡器进行替换
  3. 负载均衡器中实现环境的切换:根据当前环境,过滤调用注册中心中当前环境下的服务实例

第一步:准备配置文件

准备 nacos 配置文件,分别在 dev & test 环境创建 gateway-service 配置文件:

网关调用环境隔离案例-网关nacos配置文件

server:
  port: 5000

logging:
  level:
    root: info

spring:
  mvc:
    path-match:
      matching-strategy: ANT_PATH_MATCHER
  application:
    name: gateway-service
  cloud:
    # 切换 LocalRoundRobbinLoadBalancer
    loadbalancer:
      ribbon:
        enabled: false

    config:
      override-none: true
      allow-override: true
      override-system-properties: false
    
    # 网关配置
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: order_route                    # 路由的唯一标识
          uri: lb://nacos-order-service      # lb: load-balance 启用负载均衡,并根据服务名称自动转发
          # uri: http://nacos-order-service      # lb: load-balance 启用负载均衡,并根据服务名称自动转发
          predicates:                        # 断言规则,用于路由匹配
            - Path=/route-order-service/**
            - MyPredicate=key1,key2
          filters:
            - StripPrefix=1                  # 剔除 url 中的第一层路径: /order-service/
            # - AddRequestHeader=my-header,mh
            - MyFilter=my-header,mh
      globalcors:           # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决 options 请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的可以跨域请求, '*' 表示所有 
              - "*"
              # - "https://localhost:8001"
              # - "https://localhost:8002"
              # - "https://localhost:8003"
            allowedMethods: # 允许的跨域的请求方式, '*' 表示所有
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*"    # 允许在请求中携带的请求头, '*' 表示所有
            allowCredentials: true # 是否允许携带 cookie
            maxAge: 360000         # 跨域检测的有效期

# 测试环境服务地址
test:
  server:
    ips: 124.70.69.96

关键配置在于:

spring:
  cloud:
    # 关闭默认的负载均衡,切换 LocalRoundRobbinLoadBalancer
    loadbalancer:
      ribbon:
        enabled: false


# 测试环境服务地址
test:
  server:
    ips: 124.70.69.96

在网关服务中添加两个配置文件:

网关调用环境隔离案例-网关bootstrap配置文件
内容如下:

spring:
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      # 通用配置
      # server-addr: 127.0.0.1:8000 # 1. nacos 集群服务地址
      server-addr: 127.0.0.1:8100 # 1. nacos 服务地址
      username: nacos
      password: nacos

      # 服务治理
      discovery:
        namespace: 0298b122-a60d-47f5-9be3-9ea149f17185
        group: DEFAULT_GROUP
        service: nacos-gateway-service
        cluster-name: gateway-service-cluster
        weight: 1

      # 服务配置
      config:
        namespace: c8cc59da-dea3-44ef-9ff7-055879477406
        group: DEFAULT_GROUP
        name: gateway-service
        file-extension: yaml
        refresh-enabled: true
spring:
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      # 通用配置
      # server-addr: 127.0.0.1:8000 # 1. nacos 集群服务地址
      server-addr: 127.0.0.1:8100 # 1. nacos 服务地址
      username: nacos
      password: nacos

      # 服务治理
      discovery:
        namespace: 0298b122-a60d-47f5-9be3-9ea149f17185
        group: DEFAULT_GROUP
        service: nacos-gateway-service
        cluster-name: gateway-service-cluster
        weight: 1

      # 服务配置
      config:
        namespace: 0298b122-a60d-47f5-9be3-9ea149f17185
        group: DEFAULT_GROUP
        name: gateway-service
        file-extension: yaml
        refresh-enabled: true

唯一的区别在于配置文件的命名空间不同(dev & test):

# 服务配置
config:
  namespace: 0298b122-a60d-47f5-9be3-9ea149f17185
  
# 服务配置
config:
  namespace: c8cc59da-dea3-44ef-9ff7-055879477406

第二步:关闭默认的负载均衡器,并切换我们自己的负载均衡器

关闭默认的负载均衡器已经在上面配置了:

spring:
  cloud:
    # 关闭默认的负载均衡,切换 LocalRoundRobbinLoadBalancer
    loadbalancer:
      ribbon:
        enabled: false

注册自实现的负载均衡器:

引入 Spring 官方的原生负载均衡依赖(其中整合 Sentinel 依赖可以根据需要删除掉)

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>gateway-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway-service</name>

    <parent>
        <groupId>priv.cqq</groupId>
        <artifactId>my-mall</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <!-- nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- loadbalancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

        <!-- other -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>priv.cqq.gateway.GatewayServiceApplication</mainClass>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

在启动类中指定负载均衡配置:

@EnableDiscoveryClient
@LoadBalancerClients(defaultConfiguration = DefaultServiceLoadBalancerConfig.class)
@SpringBootApplication
public class GatewayServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayServiceApplication.class, args);
    }
}
// @Configuration // 注意,此处不能增加 @Configuration 注解,这样反而会使得下方的 @Bean 方法形参中注入不了 LoadBalancerClientFactory,说明该参数是负载均衡配置类的处理类传入的。
public class DefaultServiceLoadBalancerConfig {

    @Bean
    public ReactorLoadBalancer<ServiceInstance> myLoadBalancer(Environment environment,
                                                               LoadBalancerClientFactory loadBalancerClientFactory,
                                                               NacosDiscoveryProperties nacosDiscoveryProperties,
                                                               @Value("${spring.profiles.active}") String profile,
                                                               @Value("${test.server.ips}") String testServerIps) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        ObjectProvider<ServiceInstanceListSupplier> provider = loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class);
        return new LocalRoundRobbinLoadBalancer(provider, name, nacosDiscoveryProperties, profile, Arrays.stream(testServerIps.split(",")).collect(Collectors.toSet()));
    }
}

第三步:实现环境隔离的负载均衡器


public class LocalRoundRobbinLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private final AtomicInteger position;

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    private final String serviceId;


    // ==================================================================

    private final NacosDiscoveryProperties nacosDiscoveryProperties;

    private final String profile;

    private final Set<String> testServerIpSet;

    public LocalRoundRobbinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                        String serviceId,
                                        NacosDiscoveryProperties nacosDiscoveryProperties,
                                        String profile,
                                        Set<String> testServerIpSet) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.position = new AtomicInteger(0);
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        this.profile = profile;
        this.testServerIpSet = testServerIpSet;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        // TODO: move supplier to Request?
        // Temporary conditional logic till deprecated members are removed.
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier
                .get()
                .next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> allServerInstances) {
        if (allServerInstances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
        // 0. 默认非测试、本地开发环境,使用 serviceId 所有的可用服务实例
        List<ServiceInstance> serviceInstances = allServerInstances;
        // 1. 测试环境: 搜索测试环境中的所有服务节点
        if ("test".equals(profile)) {
            serviceInstances = findTestInstances(allServerInstances);
        }
        // 2. 本地开发环境: 搜索本地开发环境中的所有服务节点, 若为空则使用测试环境的所有服务节点
        else if ("dev".equals(profile)) {
            String ip = nacosDiscoveryProperties.getIp();
            serviceInstances = findDevInstances(allServerInstances, ip);
            if (CollectionUtils.isEmpty(serviceInstances)) {
                serviceInstances = findTestInstances(allServerInstances);
            }
        }
        if (CollectionUtils.isEmpty(serviceInstances)) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
        // 3. 轮询访问服务节点
        Response<ServiceInstance> response = roundRobbin(serviceInstances);
        ServiceInstance choose = response.getServer();
        log.info("Request {}, servers={}, choose {}",
                this.serviceId,
                serviceInstances.stream().map(item -> item.getHost() + ":" + item.getPort()).collect(Collectors.toList()),
                choose.getHost() + ":" + choose.getPort());
        return response;
    }

    /**
     * 搜索本地开发环境中的所有服务节点
     */
    private static List<ServiceInstance> findDevInstances(List<ServiceInstance> instances, String ip) {
        return instances
                .stream()
                .filter(instance -> Objects.equals(instance.getHost(), ip))
                .collect(Collectors.toList());
    }

    /**
     * 搜索测试环境中的所有服务节点
     */
    private List<ServiceInstance> findTestInstances(List<ServiceInstance> instances) {
        return instances
                .stream()
                .filter(instance -> testServerIpSet.contains(instance.getHost()))
                .collect(Collectors.toList());
    }


    /**
     * 轮询访问服务实例
     */
    private Response<ServiceInstance> roundRobbin(List<ServiceInstance> instances) {
        // 1. position 溢出后继续自增会进行递减, 只要保证数值变化是有序的, 就可以保证取模结果是有序的.
        int pos = Math.abs(this.position.incrementAndGet());
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);

        // 2. CAS 自旋修改下次调用服务的索引, 因为取模所以 position 的值不会溢出
        // int allServerCount = instances.size();
        // while (true) {
        //     int current = position.get();
        //     int next = (current + 1) % allServerCount;
        // if (position.compareAndSet(current, next)) {
        //         return new DefaultResponse(instances.get(next));
        //     }
        // }
    }
}

核心方法:getInstanceResponse,实现逻辑已经在代码注释中写的很清楚了。

最后在总结一下:根据当前的 active.profile 过滤不同 ip 的服务实例

  1. 若 dev 环境,则以当前服务运行所在主机的 ip 进行过滤(本机上启动的其他服务在 nacos 注册时使用的一定也是本机 ip)
  2. 若 test 环境,则以配置文件中的测试环境 ip 进行过滤
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值