• SpringCloud组件Ribbon的IRule的问题排查


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

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

    实现思路如下:

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

    场景定义

    实现伪代码:

    1. 定义策略
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 注入容器
    @Configuration
    public class MyRuleConfig {
        @Bean
        public IRule ribbonRule(){
            return new MyRule();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    好,一切准备就绪。

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

    明明请求的是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

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    别忘了,我们通过@Bean加入到容器中时,是单例的。问题也出在这!
    就意味着,每次初始化的时候,在设置setLoadBalancer时,就是在单例的IRule基础上,从后往前覆盖,最终IRule持有的永远都是最后一个服务的服务列表。

    大意图就是这个样子:
    在这里插入图片描述

    问题解决

    那么我们如何解决这个问题呢?
    我们知道了原因是由于单例导致的,那么我们就可以将自定义的策略改成多实例的注入。

    @Configuration
    public class MyRuleConfig {
        @Bean
        @Scope("prototype") // 将单例变为原型
        public IRule ribbonRule(){
            return new MyRule();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

  • 相关阅读:
    C语言汇总
    【JavaEE进阶序列 | 从小白到工程师】JavaEE中的Stream流的常用方法
    【月度反思】202211
    【示例】如何使用Pytorch堆叠一个神经网络
    C++学习之路-异常(exception)
    kubernetes部署rocketmq集群
    【微信小程序篇】- 多环境(版本)配置
    Keras深度学习实战(19)——使用对抗攻击生成可欺骗神经网络的图像
    labview编程笔记之事件结构
    MyBatis之xml配置的解析
  • 原文地址:https://blog.csdn.net/lkx444368875/article/details/133747715