解密Spring Cloud微服務(wù)調(diào)用:如何輕松獲取請(qǐng)求目標(biāo)方的IP和端口
目的
Spring Cloud 線上微服務(wù)實(shí)例都是2個(gè)起步,如果出問題后,在沒有ELK等日志分析平臺(tái),如何確定調(diào)用到了目標(biāo)服務(wù)的那個(gè)實(shí)例,以此來排查問題
圖片
效果
可以看到服務(wù)有幾個(gè)實(shí)例是上線,并且最終調(diào)用了那個(gè)實(shí)例
圖片
考慮到Spring Cloud在版本升級(jí)中使用了兩種負(fù)載均衡實(shí)現(xiàn),Robin和LoadBalancer,下面我們提供兩種實(shí)現(xiàn)方案
Robin實(shí)現(xiàn)方案
1. 技術(shù)棧
- Spring Cloud: Hoxton.SR6
- Spring Boot: 2.3.1.RELEASE
- Spring-Cloud-Openfeign: 2.2.3.RELEASE
2. 繼承RoundRobinRule,并重寫choose方法
/**
* 因?yàn)檎{(diào)用目標(biāo)機(jī)器的時(shí)候,如果目標(biāo)機(jī)器本身假死或者調(diào)用目標(biāo)不通無法數(shù)據(jù)返回,那么feign無法打印目標(biāo)機(jī)器。這種場(chǎng)景下我們需要在調(diào)用失敗(目標(biāo)機(jī)器沒有返回)的時(shí)候也能把目標(biāo)機(jī)器的ip打印出來,這種場(chǎng)景需要我們切入feign選擇機(jī)器的邏輯,注入我們自己的調(diào)度策略(默認(rèn)是roundrobin),在里面打印選擇的機(jī)器即可。
*/
@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實(shí)現(xiàn)方案
1. 技術(shù)棧
- Spring Cloud: 2021.0.4
- Spring Boot: 2.7.17
- Spring-Cloud-Openfeign: 3.1.4
2. 繼承ReactorServiceInstanceLoadBalancer,并實(shí)現(xiàn)相關(guān)方法
@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);
}
}
}
3.修改LoadBalancerClients配置
@Configuration
@LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfiguration.class)
public class CustomLoadBalancerConfig {
}
@Configuration
class CustomLoadBalancerConfiguration {
/**
* 參考默認(rèn)實(shí)現(xiàn)
* @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