魏晓东 发表于 2024-1-13 16:22:51

解密Spring Cloud微服务调用:如何轻松获取请求目标方的IP和端口

公众号「架构成长指南」,专注于生产实践、云原生、分布式系统、大数据技术分享。
目的

Spring Cloud 线上微服务实例都是2个起步,如果出问题后,在没有ELK等日志分析平台,如何确定调用到了目标服务的那个实例,以此来排查问题
https://files.mdnice.com/user/35072/a5c40ce9-d1cf-4d74-ad6b-c308f477d5a5.png
效果

可以看到服务有几个实例是上线,并且最终调用了那个实例
https://files.mdnice.com/user/35072/f88a239c-366b-4ab0-9bc2-650366e3d1db.png
考虑到Spring Cloud在版本升级中使用了两种负载均衡实现,Robin和LoadBalancer,下面我们提供两种实现方案
Robin实现方案

1. 技术栈


[*]Spring Cloud: Hoxton.SR6
[*]Spring Boot: 2.3.1.RELEASE
[*]Spring-Cloud-Openfeign: 2.2.3.RELEASE
2. 继承RoundRobinRule,并重写choose方法

/**
* 因为调用目标机器的时候,如果目标机器本身假死或者调用目标不通无法数据返回,那么feign无法打印目标机器。这种场景下我们需要在调用失败(目标机器没有返回)的时候也能把目标机器的ip打印出来,这种场景需要我们切入feign选择机器的逻辑,注入我们自己的调度策略(默认是roundrobin),在里面打印选择的机器即可。
*/
@Slf4j
public class FeignRule extends RoundRobinRule {

    @Override
    public Server choose(Object key) {
      Server server = super.choose(key);
      if (Objects.isNull(server)) {
            log.info("server is null");
            return null;
      }
      log.info("feign rule ---> serverName:{}, choose key:{}, final server ip:{}", server.getMetaInfo().getAppName(), key, server.getHostPort());
      return server;
    }

    @Override
    public Server choose(ILoadBalancer lb, Object key) {
      Server chooseServer = super.choose(lb, key);

      List<Server> reachableServers = lb.getReachableServers();
      List<Server> allServers = lb.getAllServers();
      int upCount = reachableServers.size();
      int serverCount = allServers.size();
      log.info("serverName:{} upCount:{}, serverCount:{}", Objects.nonNull(chooseServer) ? chooseServer.getMetaInfo().getAppName() : "", upCount, serverCount);
      for (Server server : allServers) {
            if (server instanceof DiscoveryEnabledServer) {
                DiscoveryEnabledServer dServer = (DiscoveryEnabledServer) server;
                InstanceInfo instanceInfo = dServer.getInstanceInfo();
                if (instanceInfo != null) {
                  InstanceInfo.InstanceStatus status = instanceInfo.getStatus();
                  if (status != null) {
                        log.info("serverName:{} server:{}, status:{}", server.getMetaInfo().getAppName(), server.getHostPort(), status);
                  }
                }
            }
      }

      return chooseServer;
    }
}3.修改RibbonClients配置

import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;
/**
* @description:feign 配置
*/
@Configuration
@RibbonClients(defaultConfiguration = {FeignRule.class})
public class FeignConfig {
}LoadBalancer实现方案

1. 技术栈


[*]Spring Cloud: 2021.0.4
[*]Spring Boot: 2.7.17
[*]Spring-Cloud-Openfeign: 3.1.4
2. 继承ReactorServiceInstanceLoadBalancer,并实现相关方法

@Slf4j
public class CustomRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    final AtomicInteger position;
    final String serviceId;
    ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public CustomRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
      this(serviceInstanceListSupplierProvider, serviceId, (new Random()).nextInt(1000));
    }

    public CustomRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, int seedPosition) {
      this.serviceId = serviceId;
      this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
      this.position = new AtomicInteger(seedPosition);
    }

    public Mono<Response<ServiceInstance>> choose(Request request) {
      ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
      return supplier.get(request).next().map((serviceInstances) -> {
            return this.processInstanceResponse(supplier, serviceInstances);
      });
    }

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

      return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
      if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + this.serviceId);
            }

            return new EmptyResponse();
      } else {

            int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
            ServiceInstance instance = instances.get(pos % instances.size());
            log.info("serverName:{} upCount:{}",instance.getServiceId(),instances.size());
            log.info("feign rule ---> serverName:{}, final server ip:{}:{}", instance.getServiceId(), instance.getHost(),instance.getPort());
            return new DefaultResponse(instance);
      }
    }
}2.修改LoadBalancerClients配置

@Configuration
@LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfiguration.class)
public class CustomLoadBalancerConfig {
}

@Configuration
class CustomLoadBalancerConfiguration {
    /**
   * 参考默认实现
   * @see org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration#reactorServiceInstanceLoadBalancer
   * @return
   */
    @Bean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
      String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
      return new CustomRoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}以上两部完成大功告成!
源码下载:
https://github.com/dongweizhao/spring-cloud-example/tree/SR6-OpenFeign
https://github.com/dongweizhao/spring-cloud-example/tree/EurekaOpenFeign

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 解密Spring Cloud微服务调用:如何轻松获取请求目标方的IP和端口