项目场景:
Spring Cloud Feign 学习过程中遇到Feign访问的时候报错Load balancer does not have available server for client
问题描述:
完整报错日志如下
path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: MS-CUSTOMER] with root cause
com.netflix.client.ClientException: Load balancer does not have available server for client: MS-CUSTOMER
at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) ~[ribbon-loadbalancer-2.2.2.jar:2.2.2]
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:184) ~[ribbon-loadbalancer-2.2.2.jar:2.2.2]
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180) ~[ribbon-loadbalancer-2.2.2.jar:2.2.2]
at rx.Observable.unsafeSubscribe(Observable.java:10211) ~[rxjava-1.1.10.jar:1.1.10]
at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94) ~[rxjava-1.1.10.jar:1.1.10]
at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42) ~[rxjava-1.1.10.jar:1.1.10]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.1.10.jar:1.1.10]
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.1.10.jar:1.1.10]
原因分析:
从报错信息上看Load balancer does not have available server for client: MS-CUSTOMER,表示负载均衡器没有找到有效的服务信息,此异常是 LoadBalancerContext从LoadBalancer对象中获取服务的时候, 没有找到所调用的服务信息
Server svc = lb.chooseServer(loadBalancerKey);
if (svc == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Load balancer does not have available server for client: "
+ clientName);
}
我们查询lb.chooseServer 实现的功能其实是从LoadBalancer对象中的到所有的服务, 然后根据规则从服务中获取一个匹配的服务(相关规则信息可以查看文章 Spring Cloud Ribbon负载均衡策略详解_学然后知不足!-CSDN博客)
List<Server> serverList = getLoadBalancer().getAllServers();
再看一下getAllServer接口,这个接口返回的是可用的和非可用的服务
/**
* @return All known servers, both reachable and unreachable.
*/
public List<Server> getAllServers();
查看BaseLoadBalancer 如何实现的此方法发现, 服务取自List<Server>集合
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
@Override
public List<Server> getAllServers() {
return Collections.unmodifiableList(allServerList);
}
/**
* Add a server to the 'allServer' list; does not verify uniqueness, so you
* could give a server a greater share by adding it more than once.
*/
public void addServer(Server newServer) {
if (newServer != null) {
try {
ArrayList<Server> newList = new ArrayList<Server>();
newList.addAll(allServerList);
newList.add(newServer);
setServersList(newList);
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Error adding newServer {}", name, newServer.getHost(), e);
}
}
}
/**
* Add a list of servers to the 'allServer' list; does not verify
* uniqueness, so you could give a server a greater share by adding it more
* than once
*/
@Override
public void addServers(List<Server> newServers) {
if (newServers != null && newServers.size() > 0) {
try {
ArrayList<Server> newList = new ArrayList<Server>();
newList.addAll(allServerList);
newList.addAll(newServers);
setServersList(newList);
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Exception while adding Servers", name, e);
}
}
}
解决方案:
从上面的源码分析可知,请求处理时候会从会先从一个缓存集合List<Server> 中得到可用和不可用的服务,然后根据规则Rule 过滤获可访问的服务信息并返回Server,那么获取Server为空引起报错的可能场景有如下几个
1. Feign 启动后还没有从注册中心得到List<Server> 的时候lb#chooseServer获取结果为空
第一种可能:客户端没有开启从Eureka 中获取服务列表 ,所以需要检查配置是否错误配置成了 false
- eureka.client.register-with-eureka=true
- eureka.client.fetch-registry=true
第二种可能: 客户端已经开启了从注册中心获取服务列表, 获取列表是通过任务获取,此时任务还没执行List<Server>为空, 只需要等一会应用从注册中心检索服务后,问题会自行解决。
第三种可能: 检索后发现没有得到服务, 此时很有可能是你访问的服务应用没有正确注册到注册中心引起, 正确注册后Eureka注册中心可以正确看到发布的服务信息,发布失败一般也是上面的配置出错或其他配置问题
2. 根据Rule规则过滤从List<Server>中没有找到可用的Server
第一种可能: 因为getAllServers()返回的是可用和非可用, Rule规则会过滤到可用, 所以如果你的服务已经Down那么会找不到服务, 可以通过健康检查查看服务状态是否正常
第二种可能: 检查Fegin接口配置的服务名称和要访问的服务名称是否相同,注意字母顺序
3. 其他未知原因,只能通过断点调试了