【OpenFeign】 集成 Ribbon 配置

Metadata

title: 【OpenFeign】 集成 Ribbon 配置
date: 2023-01-02 19:08
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/OpenFeign/生态/Ribbon
categories:
  - OpenFeign
keywords:
  - OpenFeign
description: 【OpenFeign】 集成 Ribbon 配置

【OpenFeign】 集成 Ribbon 配置

基于 Spring Boot 提供的自动配置功能,Ribbon 自动配置的类在org.springframework.cloud.netflix.ribbon包的RibbonClientConfigurationRibbonAutoConfiguration配置类中。

RibbonAutoConfiguration

RibbonAutoConfiguration相当于全局配置,主要是加载 Ribbon 客户端工厂、配置类工厂、重试机制工厂等,这个配置类在启动的时候就会被加载。

首先看下RibbonAutoConfiguration配置类上的注解:

// 标记为配置类Bean
@Configuration
// 注入Bean条件,RibbonClassesConditions类去判断是否注入该Bean
@Conditional({RibbonAutoConfiguration.RibbonClassesConditions.class})
// @RibbonClients为所有的Ribbon客户端提供默认配置, @RibbonClients标记该类被注册为一个 RibbonClientSpecification
@RibbonClients
//  在加载配置的类之后再加载当前类
@AutoConfigureAfter(
    name = {"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration"}
)
// 在加载配置的类之前再加载当前类
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
// 使RibbonEagerLoadProperties、ServerIntrospectorProperties配置类生效
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})

该类具有两个属性,使用@Autowired注入

@Autowired(
        required = false
    )
    // List集合,类型为RibbonClientSpecification,注入使用了@RibbonClients 的配置类。
    private List<RibbonClientSpecification> configurations = new ArrayList();
    // Ribbon的饥饿加载(eager-load)模式配置类,Ribbon进行客户端负载均衡的Client并不是在服务启动的时候就初
    // 始化好的,而是在调用的时候才会去创建相应的Client,开启加载饥饿模式,可以提前创建。
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

接下来看下这个配置类注入了那些 Bean 对象:

// HasFeatures 可以查看系统启动的一些features,进而了解系统特征。
    @Bean
    public HasFeatures ribbonFeature() {
        return HasFeatures.namedFeature("Ribbon", Ribbon.class);
    }
    //  Spring 创建 Ribbon 客户端、负载均衡器、客户端配置实例的工厂
    @Bean
    @ConditionalOnMissingBean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }
    // LoadBalancerClient为springcloud提供的负载均衡器客户端,可以查询所有的服务并挑选一个
    @Bean
    @ConditionalOnMissingBean({LoadBalancerClient.class})
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(this.springClientFactory());
    }
    // Ribbon重试工厂
    @Bean
    @ConditionalOnClass(
        name = {"org.springframework.retry.support.RetryTemplate"}
    )
    @ConditionalOnMissingBean
    public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
        return new RibbonLoadBalancedRetryFactory(clientFactory);
    }
    // 用来进行客户端配置类的获取,比如interface com.netflix.loadbalancer.IRule 规则配置类,对应一个属性=》NFLoadBalancerRuleClassName
    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }
    // 如果配置了eager-load饥饿加载模式,就注册RibbonApplicationContextInitializer上下文初始化对象
    @Bean
    @ConditionalOnProperty({"ribbon.eager-load.enabled"})
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(this.springClientFactory(), this.ribbonEagerLoadProperties.getClients());
    }

RibbonClientConfiguration

RibbonClientConfiguration 则是 Ribbon 客户端相关配置,这个类再执行第一次 Feign 客户端请求的时候才会被加载。

比如当前 Feign 客户端为 order-service,它是第一次进行请求,那么会进入获取客户端配置方法:

然后在NamedContextFactory中获取改客户端的上下文,没有的时候,会去创建。

protected AnnotationConfigApplicationContext getContext(String name) {
    // 当前上下文是否包含了该客户端order-service
        if (!this.contexts.containsKey(name)) {
            synchronized(this.contexts) {
                // 没有则创建并放入
                if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, this.createContext(name));
                }
            }
        }
        // 有直接返回
        return (AnnotationConfigApplicationContext)this.contexts.get(name);
    }

最终调用NamedContextFactory的 createContext 方法,为每个客户端创建上下文。

通过NamedContextFactory机制,按不同的应用创建了不同的子 ApplicationContext,从而隔离不互相影响,每一个 Feign 客户端配置不同的应用上下文,这样可以让每个不同的客户端使用不同的配置、不同的 Bean。

在 createContext 方法创建了客户端的上下文后,进入刷新,则会开始加载直接当前 Context 需要的 Bean 、前置后置处理器等流程。RibbonClientConfiguration就是在这个时候加载的。

// 在获取一个@FeignClient接口对应的代理对象前,先为其创建Context对象
    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 查询RibbonAutoConfiguration 中,使用了@RibbonClients 的配置类
        if (this.configurations.containsKey(name)) {
            // 配置类中有 名称为当前客户端的配置,则添加配置并注册到上下文中
            Class[] var3 = ((NamedContextFactory.Specification)this.configurations.get(name)).getConfiguration();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Class<?> configuration = var3[var5];
                context.register(new Class[]{configuration});
            }
        }
        // 循环使用了@RibbonClients 的配置类
        Iterator var9 = this.configurations.entrySet().iterator();

        while(true) {
            Entry entry;
            do {
                if (!var9.hasNext()) {
                    // 注册
                    context.register(new Class[]{PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType});
                    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName, Collections.singletonMap(this.propertyName, name)));
                    if (this.parent != null) {
                        context.setParent(this.parent);
                        context.setClassLoader(this.parent.getClassLoader());
                    }

                    context.setDisplayName(this.generateDisplayName(name));
                    // 刷新上下文
                    context.refresh();
                    return context;
                }

                entry = (Entry)var9.next();
             // 配置类不是以default. 开头,则进入do 循环,这里没有
            } while(!((String)entry.getKey()).startsWith("default."));
            
            Class[] var11 = ((NamedContextFactory.Specification)entry.getValue()).getConfiguration();
            int var12 = var11.length;
            // 循环默认配置,并注册
            for(int var7 = 0; var7 < var12; ++var7) {
                Class<?> configuration = var11[var7];
                context.register(new Class[]{configuration});
            }
        }
    }

可以看到默认有两个 @RibbonClients 的配置类:

创建上下文后,可以看到,当前客户端的上下文信息。

RibbonClientConfiguration类使用@Import导入了其他配置类。

@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})

RibbonClientConfiguration类的属性如下:

// 默认的连接超时时间,默认1秒
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
    // 默认的读取超时时间,默认1秒
    public static final int DEFAULT_READ_TIMEOUT = 1000;
    public static final boolean DEFAULT_GZIP_PAYLOAD = true;
    // @RibbonClientName Ribbon客户端名称
    @RibbonClientName
    private String name = "client";
    // 配置工厂,由RibbonAutoConfiguration 注入
    @Autowired
    private PropertiesFactory propertiesFactory;

最后看下RibbonClientConfiguration注入了哪些 Bean :

// 当前客户端配置 
    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        // 默认的配置类
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        // 加载配置
        config.loadProperties(this.name);
        // 设置配置 
        config.set(CommonClientConfigKey.ConnectTimeout, 1000);
        config.set(CommonClientConfigKey.ReadTimeout, 1000);
        config.set(CommonClientConfigKey.GZipPayload, true);
        return config;
    }
    
    // 加载负载均衡算法,默认ZoneAvoidanceRule 轮询。
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, this.name)) {
            return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
        } else {
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
        }
    }
    // Ping 检测服务健康状态
    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        return (IPing)(this.propertiesFactory.isSet(IPing.class, this.name) ? (IPing)this.propertiesFactory.get(IPing.class, config, this.name) : new DummyPing());
    }
    // Ribbon的服务列表 
    @Bean
    @ConditionalOnMissingBean
    public ServerList<Server> ribbonServerList(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerList.class, this.name)) {
            return (ServerList)this.propertiesFactory.get(ServerList.class, config, this.name);
        } else {
            ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
            serverList.initWithNiwsConfig(config);
            return serverList;
        }
    }
    // 服务列表更新器
    @Bean
    @ConditionalOnMissingBean
    public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
        return new PollingServerListUpdater(config);
    }
    // 负载均衡器ILoadBalancer
    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
    }
    // Ribbon的LoadBalancer五大组件之ServerListFilter服务列表过滤器
    @Bean
    @ConditionalOnMissingBean
    public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerListFilter.class, this.name)) {
            return (ServerListFilter)this.propertiesFactory.get(ServerListFilter.class, config, this.name);
        } else {
            ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
            filter.initWithNiwsConfig(config);
            return filter;
        }
    }
    // 负载均衡器上下文
    @Bean
    @ConditionalOnMissingBean
    public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer, IClientConfig config, RetryHandler retryHandler) {
        return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
    }
    // 重试处理器
    @Bean
    @ConditionalOnMissingBean
    public RetryHandler retryHandler(IClientConfig config) {
        return new DefaultLoadBalancerRetryHandler(config);
    }
    // 服务拦截器
    @Bean
    @ConditionalOnMissingBean
    public ServerIntrospector serverIntrospector() {
        return new DefaultServerIntrospector();
    }
    // 后置处理器
    @PostConstruct
    public void preprocess() {
        RibbonUtils.setRibbonProperty(this.name, CommonClientConfigKey.DeploymentContextBasedVipAddresses.key(), this.name);
    }

总结

项目启动 =》RibbonAutoConfiguration(加载工厂类,默认配置)=》第一次访问 =》代理客户端 =》初始化该客户端上下文 =》RibbonClientConfiguration(加载服务列表、过滤器、拦截器、客户端配置等)=》处理请求。