公众号「架构成长指南」,专注于生产实践、云原生、分布式系统、大数据技术分享。
目的
Spring Cloud 线上微服务实例都是2个起步,如果出问题后,在没有ELK等日志分析平台,如何确定调用到了目标服务的那个实例,以此来排查问题
效果
可以看到服务有几个实例是上线,并且最终调用了那个实例
考虑到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),在里面打印选择的机器即可。*/@Slf4jpublic 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 reachableServers = lb.getReachableServers(); List 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,并实现相关方法
@Slf4jpublic class CustomRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer { final AtomicInteger position; final String serviceId; ObjectProvider serviceInstanceListSupplierProvider; public CustomRoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId) { this(serviceInstanceListSupplierProvider, serviceId, (new Random()).nextInt(1000)); } public CustomRoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId, int seedPosition) { this.serviceId = serviceId; this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; this.position = new AtomicInteger(seedPosition); } public Mono<Response> choose(Request request) { ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new); return supplier.get(request).next().map((serviceInstances) -> { return this.processInstanceResponse(supplier, serviceInstances); }); } private Response processInstanceResponse(ServiceInstanceListSupplier supplier, List serviceInstances) { Response serviceInstanceResponse = this.getInstanceResponse(serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } private Response getInstanceResponse(List 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 {}@Configurationclass CustomLoadBalancerConfiguration { /** * 参考默认实现 * @see org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration#reactorServiceInstanceLoadBalancer * @return */ @Bean public ReactorLoadBalancer 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