-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spring Cloud Ribbon踩坑记录及原理解析 #29
Comments
你好,请教一下CustomHttpRequestInterceptor 怎么添加到 LoadBalancerInterceptor 之后 |
@Tinkerc 因为要获取到实际的IP,因此必须保证你添加的拦截器的代码在ribbon初始化的地方之后执行。 有几种方式,比如你可以监听Spring初始化完成事件,然后注入RestTemplate,把自己自定义的拦截器添加进去。 |
@aCoder2013 嗯,我也是要获取实际IP,可以把关键源码贴出来一下吗,重新注入RestTemplate是否还需要单独设置@LoadBalanced |
嗯,重新注入的是ribbon初始化完成之后的RestTemplate,只需要给RestTemplate再添加一个拦截器即可 @Resource
private RestTemplate restTemplate;
private AtomicBoolean started = new AtomicBoolean(false);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event != null) {
if (started.compareAndSet(false, true)) {
/*
Spring初始化完成后再执行
*/
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (interceptors == null) {
interceptors = new ArrayList<>();
}
interceptors.add(new LogClientHttpRequestInterceptor());
restTemplate.setInterceptors(interceptors);
}
}
} |
你好目前我也遇到了这个问题,在spring boot 1.5x集成ribbon和feign。思想是通过自定义规则实现某些特殊请求规则定义。但是和你记录的一样当请求返回时。rule的lb选择错误导致了404,自定义的规则不能去掉。请问第二种解法能详细说一说吗? |
@lzn312 可以参考下官方的文档,用 |
@aCoder2013 非常感谢你的回复,该问题已经通过配置@RibbonClients解决 |
博主您好,我是电子工业出版社博文视点的编辑,看到您发表的文章,觉得内容很好,您是否有兴趣出版图书呢:) 我的微信是472954195 |
你好,是要在@RibbonClients中再次配置IRule么 |
非常感谢,终于找到一个花了心思的靠谱解释 |
@AKwang100 很高兴能帮到你 |
你好,我的微信是Tnsg_ui_lip,想请教一下你这个问题,我也遇到这个情况,但是我们没有定义IRule,我暂时加了discoverClient#getInstances打印某个服务所有的uri |
麻烦问下如何解决的哇我用了@RibbonClients但是还是404 |
现象
前两天碰到一个ribbon相关的问题,觉得值得简单记录一下。表象是对外的接口返回内部异常,这个是封装的统一错误信息,Spring的异常处理器catch到未捕获异常统一返回的信息。因此到日志平台查看实际的异常:
这里介绍一下背景,出现问题的开放网关,做点事情说白了就是转发对应的请求给后端的服务。这里用到了ribbon去做服务负载均衡、eureka负责服务发现。
这里出现404,首先看了下请求的url以及对应的参数,都没有发现问题,对应的后端服务也没有收到请求。这就比较诡异了,开始怀疑是ribbon或者Eureka的缓存导致请求到了错误的ip或端口,但由于日志中打印的是Eureka的serviceId而不是实际的ip:port,因此先加了个日志:
这里是通过给RestTemplate添加拦截器的方式,但要注意,ribbon也是通过给RestTemplate添加拦截器实现的解析serviceId到实际的ip:port,因此需要注意下优先级添加到ribbon的
LoadBalancerInterceptor
之后,我这里是通过Spring的初始化完成事件的回调中添加的,另外也添加了另一条日志,在catch到这个异常的时候,利用Eureka的DiscoveryClient#getInstances
获取到当前的实例信息。之后在测试环境中复现了这个问题,看了下日志,eurek中缓存的实例信息是对的,但是实际调用的确实另外一个服务的地址,从而导致了接口404。
源码解析
从上述的信息中可以知道,问题出在ribbon中,具体的原因后面会说,这里先讲一下Spring Cloud Ribbon的初始化流程。
注意这个注解
@RibbonClients
, 如果想要覆盖Spring Cloud提供的默认Ribbon配置就可以使用这个注解,最终的解析类是:atrrs包含defaultConfiguration,因此会注册RibbonClientSpecification类型的bean,注意名称以
default.
开头,类型是RibbonAutoConfiguration,注意上面说的RibbonAutoConfiguration被@RibbonClients修饰。然后再回到上面的源码:
注意这里的SpringClientFactory, ribbon默认情况下,每个eureka的serviceId(服务),都会分配自己独立的Spring的上下文,即ApplicationContext, 然后这个上下文中包含了必要的一些bean,比如:
ILoadBalancer
、ServerListFilter
等。而Spring Cloud默认是使用RestTemplate封装了ribbon的调用,核心是通过一个拦截器:因此核心是通过这个拦截器实现的负载均衡:
然后将请求转发给LoadBalancerClient:
而这里的LoadBalancer是通过上文中提到的SpringClientFactory获取到的,这里会初始化一个新的Spring上下文,然后将Ribbon默认的配置类,比如说:
RibbonAutoConfiguration
、RibbonEurekaAutoConfiguration
等添加进去, 然后将当前spring的上下文设置为parent,再调用refresh方法进行初始化。最核心的就在这一段,也就是说对于每一个不同的serviceId来说,都拥有一个独立的spring上下文,并且在第一次调用这个服务的时候,会初始化ribbon相关的所有bean, 如果不存在 才回去父context中去找。
再回到上文中根据分流策略获取实际的ip:port的代码段:
也就是说最终会调用

IRule
选择到一个节点,这里支持很多策略,比如随机、轮训、响应时间权重等:这里的LoadBalancer是在BaseLoadBalancer的构造器中设置的,上文说过,对于每一个serviceId服务来说,当第一次调用的时候会初始化对应的spring上下文,而这个上下文中包含了所有ribbon相关的bean,其中就包括ILoadBalancer、IRule。
原因
通过跟踪堆栈,发现不同的serviceId,IRule是同一个, 而上文说过,每个serviceId都拥有自己独立的上下文,包括独立的loadBalancer、IRule,而IRule是同一个,因此怀疑是这个bean是通过parent context获取到的,换句话说应用自己定义了一个这样的bean。查看代码果然如此。
这样就会导致一个问题,IRule是共享的,而其他bean是隔离开的,因此后面的serviceId初始化的时候,会修改这个IRule的LoadBalancer, 导致之前的服务获取到的实例信息是错误的,从而导致接口404。
解决方案
解决方法也很简单,最简单就将这个自定义的IRule的bean干掉,另外更标准的做法是使用RibbonClients注解,具体做法可以参考文档。
总结
核心原因其实还是对于Spring Cloud的理解不够深刻,用法有错误,导致出现了一些比较诡异的问题。对于自己使用的组件、框架、甚至于每一个注解,都要了解其原理,能够清楚的说出这个注解有什么效果,有什么影响,而不是只着眼于解决眼前的问题。
The text was updated successfully, but these errors were encountered: