栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Cloud Ribbon 详解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Cloud Ribbon 详解

Ribbon是什么

Ribbon是一个客户端负载均衡器,它赋予了应用一些支配HTTP与TCP行为的能力,这里的负载均衡是客户端的负载均衡,也有人称为后端负载均衡是进程内负载均衡的一种。Ribbon是SpringCloud生态里的不可缺少的组件,有了它,是个服务的横向扩展更加方便了。此外想Feign和Zuul默认是集成了Ribbon。

Ribbon是Neflix开源的一个组件,目前Ribbon早已进入维护状态,但是就目前的情况来看,Spring Cloud Netflix的一些组件还是可以使用。

Spring Cloud Loadbalancer是Spring Cloud社区开源的组件,目的是替代进入维护状态的Ribbon,但是Loadbalancer还是有很长的一段路要走。
代买很简单
官网下载例子
https://spring.io/projects/spring-cloud-alibaba#samples


官网完整的demo

Ribbon实战

从上一节可以看到,开启负载均衡只需通过@LoadBalanced注解即可。负载均衡中又很多的负载均衡策略,如轮询(Round Robin)、权重(Weight)、ip_hash等。这些丰富的策略让我们在构建应用的时候,有很多选择的余地,可以根据实际的业务场景选择最合适的策略。
在Ribbon中一共提供了7中负载均衡策略:

1、RandomRule 随机策略 随机选择Server

2、RoundRobinRule 轮询策略 按顺序选择Server 默认

3、RetryRule 重试策略 在一个配置时间段内当选择Server不成功,则一直尝试选择一个可用的Server

4、BestAvailableRule 最低并发策略 逐个考察Server,如果Server断路器打开,则忽略,再选择其中并发连接最低的Server

5、AvailabilityFilteringRule 可用过滤策略 过滤一直连接失败并标记为circuit tripped的Server,过滤掉那些高并发连接的Server(active connections超过配置的阈值)

6、ResponseTimeWeightedRule 响应时间加权策略 已经被弃用,作用同WeightedResponseTimeRule
WeightedResponseTimeRule 响应时间加权策略 根据Server的响应时间分配权重,响应时间越长,权重越低,被选中的概率就越低。响应时间越短,权重越高,被选择到的概率越高

7、ZoneAvoidanceRule 区域权衡策略 综合判断Server所在区域的性能和Server的可用性轮询选择Server,并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有Server

Ribbon自定义配置负载均衡策略

全局配置
使用Ribbon时配置全局的负载均衡策略,需要加一个配置类。改配置类需要被@ComponentScan扫描到才能全局生效。

@Configuration
public class GlobalRuleConfig {
    @Bean
    public IRule ribbonRule() {
        return new RandomRule();
    }
}

启动类的配置

@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "ribbon-service-b", configuration = AnnoRuleConfig.class)
public class ServiceAApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceAApplication.class, args);
    }

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

通过@RibbonClient指定某个服务的负载均衡策略,其他没有被指定的,就是用默认的负载均衡策略。该注解可以把其他的配置类作为另外一个IOC容器导入到应用中,相当于加载了两个完全不相干的Spring的beans配置文件,此时应用中会有两个IOC容器。

@RibbonClient(name = "RIBBON-SERVICE-B", configuration = AnnoRuleConfig.class)

也可以使用一下的方式,指定多个服务的负载均衡策略
@RibbonClients(value = {
@RibbonClient(name = “RIBBON-SERVICE-B”, configuration = AnnoRuleConfig.class),
@RibbonClient(name = “RIBBON-SERVICE-C”, configuration = AnnoRuleConfig.class)
})
基于配置文件
下面对服务ribbon-service-b的负载均衡策略使用

RIBBON-SERVICE-B:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Ribbon超时与重试

使用HTTP发起请求难免会发生问题,在F版开始Ribbon的重试机制默认是开启的,需要添加对超时时间与重试策略的配置。列入下面ribbon-service-b服务的配置

RIBBON-SERVICE-B:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    ConnectTimeout: 3000
    ReadTimeout: 60000
    MaxAutoRetries: 3 #对第一次请求的服务的重试次数
    MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
    OkToRetryOnAllOperations: true

也可以全局配置

ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000
  MaxAutoRetries: 3 #对第一次请求的服务的重试次数
  MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
  OkToRetryOnAllOperations: true

一般Ribbon都是搭配OpenFeign这类Http客户端或者其他RPC使用。因为这样去调用远程服务会更加优雅和方便。而OpenFeign默认是继承了Ribbon,对于Ribbon的超时时间配置也是很简单。

对于网络抖动这些可以使用spring-retry,spring-retry是spring提供的一个基于spring的重试框架,非常好用。

Ribbon饥饿加载
Ribbon在进行客户端负载均衡的时候,并不是启动时就加载上下文,而是在实际请求的时候采取创建。因为要加载上下文的原因,在第一次调用时可能会很慢,甚至导致超时。所以我们可以指定Ribbon客户端开启立即加载(饥饿加载),在应用启动的时候就立即加载所有配置项的应用程序上下文。

ribbon:
  eager-load:
    clients: ribbon-service-b, ribbon-service-order
    enabled: true
自定义Ribbon客户端

在Ribbon的1.2.0版本之后,就可以使用配置文件来定制Ribbon客户端,其实实质就是使用配置文件来指定一些默认加载类,从而更改Ribbon客户端的行为,并且使用这种方式优先级最高,优先级高于使用注解@RibbonClient指定的配置和源码中加载的相关的Bean。看下表:

RIBBON-SERVICE-B:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    NiWSServerListClassName: com.netflix.loadbalancer.ConfigurationbasedServerList
Ribbon进阶 核心工作原理

Ribbon的核心接口:

Ribbon完全是基于这些接口上建立起来的,是Ribbon的核心。了解这些核心的类的功能对于理解Ribbon的原理和扩展很有利。

在之前的例子中,使用Ribbon负载均衡都是通过在RestTemplate的Bean上添加注解@LoadBalanced,使得RestTemplate拥有了负载均衡的能力。
LoadBalanced源码:


@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

在注释中可以看到:该注解标记在RestTemplate或者其他的WebClient的Bean上,来使用LoadBalancerClient。

LoadBalancerClient:该接口扩展自ServiceInstanceChooser

public interface LoadBalancerClient extends ServiceInstanceChooser {

    
     T execute(String serviceId, LoadBalancerRequest request) throws IOException;

    
     T execute(String serviceId, ServiceInstance serviceInstance,
            LoadBalancerRequest request) throws IOException;

    
    URI reconstructURI(ServiceInstance instance, URI original);
}

ServiceInstanceChooser:

public interface ServiceInstanceChooser {

    
    ServiceInstance choose(String serviceId);
}

ServiceInstance choose(String serviceId):根据ServiceId,结合负载均衡器选择一个服务实例
T execute(String serviceId, LoadBalancerRequest request):使用来自LoadBalancer的ServiceInstance为指定的服务执行请求
T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request):使用来自LoadBalancer的ServiceInstance为指定的服务执行请求,是上一个方法的重载,在实现类中可以看到它们的关系,就是前一个方法的细节实现
URI reconstructURI(ServiceInstance instance, URI original):使用注解ip和port构建特定的URL以供Ribbon内部使用。Ribbon使用具有逻辑服务名称的URL作为host,例如:http://service-b/order/add。

从这些方法的功能可以知道这两个接口的重要性了。这两个接口的同一包下有一个类LoadBalancerAutoConfiguration。LoadBalancerAutoConfiguration在org.springframework.cloud.client.loadbalancer包下,在spring-cloud-commons里面。该自动配置类正式Ribbon的核心配置类。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    @LoadBalanced
    @Autowired(required = false)
    private List restTemplates = Collections.emptyList();

    @Autowired(required = false)
    private List transformers = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    }

    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(
            LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {

        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

    }

    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryFactory loadBalancedRetryFactory() {
            return new LoadBalancedRetryFactory() {
            };
        }

    }

    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryInterceptorAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRetryProperties properties,
                LoadBalancerRequestFactory requestFactory,
                LoadBalancedRetryFactory loadBalancedRetryFactory) {
            return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
                    requestFactory, loadBalancedRetryFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

    }

}

这就清晰起来了,他的配置加载时机是当前工程环境必须有RestTemplate的实例和必须初始化了LoadBalancerClient的实现类。

@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)

LoadBalancerRequestFactory:用于创建LoadBalancerRequest给LoadBalancerInterceptor使用。

LoadBalancerInterceptorConfig:维护了LoadBalancerInterceptor与RestTemplateCustomizer的实例。

LoadBalancerInterceptor:拦截每一次的HTTP请求,将请求绑定金Ribbon的负载均衡的生命周期。
RestTemplateCustomizer:为每一个Restemplate绑定LoadBalancerInterceptor拦截器。
LoadBalancerInterceptor的作用已经和贴近答案了。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancer;

    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
            LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        // for backwards compatibility
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null,
                "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName,
                this.requestFactory.createRequest(request, body, execution));
    }
}

在源码可以看到它是通过ClientHttpRequestInterceptor实现每次对HTTP请求的拦截,ClientHttpRequestInterceptor类是Spring中维护的请求拦截器,实现它的intercept方法就可以使得请求进入方法内,从而Ribbon就可以做一些自己的处理了。

在使用RestTemplate请求服务时使用的URI:http://serviceName/path/to/se…

LoadBalancerInterceptor中的intercept方法,最终调用的是RibbonLoadBalancerClient的execute方法:

    public  T execute(String serviceId, LoadBalancerRequest request, Object hint) throws IOException {
        // 拿到负载均衡器的实现
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
        // 拿到具体的Server
        Server server = this.getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getmetadata(server));
            return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
        }
    }
    protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
        return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
    }

对于chooseServer是接口ILoadBalancer的方法,这里就先看一下其中的一个实现baseLoadBalancer

    public Server chooseServer(Object key) {
        if (this.counter == null) {
            this.counter = this.createCounter();
        }

        this.counter.increment();
        if (this.rule == null) {
            return null;
        } else {
            try {
                return this.rule.choose(key);
            } catch (Exception var3) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});
                return null;
            }
        }
    }

最后是通过:rule.choose(key)拿到Server,而rule就是IRule。

在RibbonClientConfiguration中初始化了上面表格提到几个核心类

初始化ribbonRule: ZoneAvoidanceRule
初始化ribbonPing:DummyPing
初始化ribbonServerList:ConfigurationbasedServerList
初始化ServerListUpdater:new PollingServerListUpdater(config)
初始化ILoadBalancer:ZoneAwareLoadBalancer
初始化ribbonServerListFilter:ZonePreferenceServerListFilter
初始化ribbonLoadBalancerContext:RibbonLoadBalancerContext
初始化serverIntrospector:DefaultServerIntrospector
关于BlockingLoadBalancerClient:

Spring Cloud Hoxton.RELEASE 版本发布之后,新增了一个新的负载均衡器实现BlockingLoadBalancerClient。它是第一个包含阻塞式和非阻塞式负载均衡器客户端实现的版本,作为已进入维护模式的Netflix Ribbon的替代方案。

如果想在 RestTemplate使用新的 BlockingLoadBalancerClient, 需要增加 spring-cloud-loadbalancer 的依赖,否则默认使用RibbonLoadBalancerClient。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/644247.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号