SpringCloud组件Ribbon的IRule的问题排查

最近很久没有写文章啦,刚好遇到了一个问题,其实问题也挺简单,但是还是得对源码有一定了解才能够发现。

最近在实现一个根据请求流量的标签,将请求转发到对应的节点,其实和俗称的灰度请求有点相似,

实现思路如下:

  • 首先为特定节点打上标签
  • 通过截取请求中的header的标签key,然后存入上下文中
  • 在服务转发时(Feign),在负载均衡步骤前将节点的标签和请求标签相匹配,筛选出标签节点。
  • 将标签节点进行策略选择一个合适的节点然后转发。

场景定义

实现伪代码:

  1. 定义策略
java 复制代码
public class MyRule extends AbstractLoadBalancerRule {
 
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }
    @Override
    public Server choose(Object o) {
        return this.choose(this.getLoadBalancer(),o);
    }

    public Server choose(ILoadBalancer lb, Object key) {
       		// 标签匹配
       		// 节点选择
       		// 策略选举
            return server;
        }
    }
}
  1. 注入容器
java 复制代码
@Configuration
public class MyRuleConfig {
    @Bean
    public IRule ribbonRule(){
        return new MyRule();
    }
}

好,一切准备就绪。

在应用当中很快就遇到了问题,在选择过程中,节点地址出现错乱。

明明请求的是A服务节点,结果转发的是B节点

通过调试发现ILoadBalancer对象有问题,比如A服务节点持有的节点列表竟然是B的节点列表。

ILoadBalancer: 每个服务都独立持有一个独属于该服务负载列表。比如A服务持有的就是A服务列表,B就是B的.但此时却出现了归属于A的ILoadBalancer中节点列表竟然都是B的。

原因梳理

最终排查发现就是注入的方式问题,为什么这么说呢?

由于服务节点在初始化的过程中,都是以服务名作为一个独立配置存在于容器中的:(如下图)

用户服务单独有一套负载均衡规则(IRule),同理order订单服务也是单独一套负载均衡规则,双方各自持有了各自的服务列表(ILoadBalancer)。

但是由于我们要改写IRule的实现,同时注入到容器中,让服务能够获取到我改写的实现,我们直接@Bean给加入了。

此时在初始化这个IRule的时候就会出现问题,因为IRule内部是持有ILoadBalancer的,但是ILoadBalancer针对每个服务都是不一样的。

我们看一下IRule是怎么被加载到用户服务上下文的:
com.netflix.loadbalancer.BaseLoadBalancer#setRule

java 复制代码
public void setRule(IRule rule) {
    if (rule != null) { // 此时我们是从容器获取到的,默认是个单例
        this.rule = rule;
    } else {
        /* default rule */
        this.rule = new RoundRobinRule();
    }
    
    if (this.rule.getLoadBalancer() != this) { // 肯定满足
    	// 将自身交给rule
        this.rule.setLoadBalancer(this);
    }
}

别忘了,我们通过@Bean加入到容器中时,是单例的。问题也出在这!

就意味着,每次初始化的时候,在设置setLoadBalancer时,就是在单例的IRule基础上,从后往前覆盖,最终IRule持有的永远都是最后一个服务的服务列表。

大意图就是这个样子:

问题解决

那么我们如何解决这个问题呢?

我们知道了原因是由于单例导致的,那么我们就可以将自定义的策略改成多实例的注入。

java 复制代码
@Configuration
public class MyRuleConfig {
    @Bean
    @Scope("prototype") // 将单例变为原型
    public IRule ribbonRule(){
        return new MyRule();
    }
}

此时每次获取都是一个新的对象,相互不在影响。同理你如果需要覆盖RibbonClientConfiguration配置类中的对象时,也需要避免使用单例模式去定义它!

相关推荐
AAA修煤气灶刘哥33 分钟前
缓存这「加速神器」从入门到填坑,看完再也不被产品怼慢
java·redis·spring cloud
AAA修煤气灶刘哥1 小时前
接口又被冲崩了?Sentinel 这 4 种限流算法,帮你守住后端『流量安全阀』
后端·算法·spring cloud
✎﹏赤子·墨筱晗♪2 小时前
从反向代理到负载均衡:Nginx + Tomcat 构建高可用Web服务架构
nginx·tomcat·负载均衡
叶绪2582 小时前
Nginx 反向代理 + Tomcat 集群:负载均衡配置步骤与核心原理
nginx·tomcat·负载均衡
T_Ghost5 小时前
SpringCloud微服务服务容错机制Sentinel熔断器
spring cloud·微服务·sentinel
喂完待续7 小时前
【序列晋升】28 云原生时代的消息驱动架构 Spring Cloud Stream的未来可能性
spring cloud·微服务·云原生·重构·架构·big data·序列晋升
荣光波比9 小时前
Nginx 实战系列(四)—— Nginx反向代理与负载均衡实战指南
运维·nginx·云计算·负载均衡
惜.己21 小时前
Docker启动失败 Failed to start Docker Application Container Engine.
spring cloud·docker·eureka
chenrui3101 天前
Spring Boot 和 Spring Cloud: 区别与联系
spring boot·后端·spring cloud