客户端负载均衡Spring Cloud Ribbon简介

栏目: 服务器 · 发布时间: 5年前

内容简介:这里提供一个能打印实际调用的服务IP与端口定制Bean1、重试基本配置,key值使用的是ribbon下面的,但实际重试的功能为RetryTemplate封装2、上述配置无法满足连接超时的时候重试所有请求(包括POST),可以通过定制RibbonLoadBalancedRetryPolicy来达到上述需求

版权声明:可以任意转载,转载时请注明原文出处

简介

系统微服务化后,内部服务之间调用需要一个专业、可靠的方式来实现,这个方式最少需要支持客户端负载均衡、重试、熔断等功能;而Ribbon正是一个较好的选择,尤其是通过SpringCloud的封装之后,Ribbon很好的集成了Eureka、RetryTemplate、RestTemplate等类库,轻易的实现了上述功能,让服务之间的调用变得简单.

目标

  1. 本文先会单独介绍SpringCloud Ribbon相关的各个组件,让大家对这些组件有一个基本了解;
  2. 通过配置一个具有客户端负载均衡、重试、熔断能力的RestTemplate,带大家稍微深入各个组件的内部原理以及初探各个组件之间的运作机制;
  3. 能够解答下面常见疑问:
    a. 我的服务配置的url是 http://serviceName/function,它是怎么知道调用到哪个IP端口去的?
    b. 为什么请求还在往已经挂掉的服务器上发?
    c. 为什么同一笔交易发了3次?
    d. 我只想在连接超时时进行重试怎么做?

组件简介

RestTemplate

Spring Web自带的用于发起发起HTTP请求的客户端,几个关键参数如下:

1. HttpMessageConverter,用于将RestTemplate中的请求或者返回对象转换成对应的底层HttpInputStream或者HttpOutPutStream,一般情况用默认值即可,有特定需要可以自己定制;

2. RequestFactory,用于创建实际发起http请求对象的工厂类,默认为SimpleClientHttpRequestFactory,基本能够满足需要;对性能有要求的可以使用基于HttpClient的RequestFactory;另外需要格外注意RequestFactory的连接与读取超时需要根据业务场景配置合理的值;

3. Interceptors,用于拦截http请求,做一些额外的处理,例如打印请求返回日志、发送请求调用链信息到Zipkin和接下来要讲的客户端负载均衡;

Ribbon

Ribbon是一个客户端的软负载均衡器,几个关键参数如下:

1. ServerList:维护一个静态或动态的(例如通过Eureka)服务列表;

2. Rule:负载均衡算法,例如轮询、加权轮询、随机、最高可用等;

3. Ping:判断服务列表中的服务是否健康,不健康则移除;

4. ServerListFilter:过滤宕机的服务或者过滤跨机房的服务;

RetryTemplate

Spring提供的重试工具,几个关键参数如下:

1. retryPolicy:重试策略,配置重试次数,什么时候重试等;

2. backOffPolicy:重试间隔时间,例如每个100ms重试一次

3. listeners:可以监听重试过程中的事件,例如重试前、重试后记录一条日志等

Hystrix

Hystrix是一个通过提供熔断、降级等逻辑来帮助提高分布式系统总体容错能力的类库,这里不做详细介绍

组件内部运作简介

上述组件之间的基本关系如下图:

客户端负载均衡Spring Cloud Ribbon简介

1. RestTemplate负责实际的HTTP调用,全部脏活累活都是这个哥们在做;

2. Ribbon负责客户端负载均衡,通过在RestTemplate中添加一个LoadBalancerInterceptor实现,详见LoadBalancerAutoConfiguration源码;

3. RetryTemplate负责重试,同样也是通过RestTemplate中添加Interceptor实现,只不过换成了RetryLoadBalancerInterceptor;重试策略由LoadBalancedRetryPolicyFactory创建;

4. Hystrix包装上述运作过程,相对独立,只有一个超时时间参数的计算与上述重试次数等相关;

组件参数配置详解

RestTemplate配置

这里配置一个基于SimpleClientHttpRequestFactory的RestTemplate,并设置连接超时与读取超时时间。

/**
* load balanced rest template
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// 这里可以根据需要替换成HttpComponentsClientHttpRequestFactory
SimpleClientHttpRequestFactory sf = new SimpleClientHttpRequestFactory();
// 设置连接超时时间
sf.setConnectTimeout(200);
// 设置读取超时时间
sf.setReadTimeout(2000);
restTemplate.setRequestFactory(sf);
return restTemplate;
}

Ribbon配置

Ribbon通过SpringBoot自动配置即可,集成Eureka之后的默认配置如下:

配置项 默认值 说明
serverList DiscoveryEnabledNIWSServerList 基于Eureka注册中心自动发现的服务列表
Rule ZoneAvoidanceRule 区域感知+轮询负载+均衡规则
Ping NIWSDiscoveryPing 不实际Ping后台,单纯已注册中心结果为准的Ping规则
ServerListFilter ZonePreferenceServerListFilter 根据区域过滤服务

这里提供一个能打印实际调用的服务IP与端口定制Bean

/**
     * 定制的 RibbonLoadBalancerClient
     * 打印实际请求的IP与端口
     */
    @Bean
    public LoadBalancerClient loadBalancerClient(SpringClientFactory springClientFactory) {
        return new RibbonLoadBalancerClient(springClientFactory) {
            @Override
            public URI reconstructURI(ServiceInstance instance, URI original) {
                URI reconstructedURI = super.reconstructURI(instance, original);
                logger.info("reconstructedURI: {}", reconstructedURI);
                return reconstructedURI;
            }
        };
    }

RetryTempalte配置

1、重试基本配置,key值使用的是ribbon下面的,但实际重试的功能为RetryTemplate封装

ribbon:
  # 重试当前实例次数
  MaxAutoRetries: 0
  # 重试下一个实例次数
  MaxAutoRetriesNextServer: 1
  # 是否所有请求都重试,false的话只重试GET请求
  OkToRetryOnAllOperations: false
  # 配置可以重试的HTTP返回码
  retryableStatusCodes: 503

2、上述配置无法满足连接超时的时候重试所有请求(包括POST),可以通过定制RibbonLoadBalancedRetryPolicy来达到上述需求

/**
 * 定制的RibbonLoadBalancedRetryPolicy
 * 支持连接超时、连接拒绝的情况下,重试所有请求
 */
public class MyRibbonLoadBalancedRetryPolicy extends RibbonLoadBalancedRetryPolicy {

    private static final BinaryExceptionClassifier retryableClassifier;
    private static final String CONNECT_TIMED_OUT = "connect timed out";

    static {
        Collection<Class<? extends Throwable>> exceptionClasses = new ArrayList<>();
        // 根据RestTemplate的RequestFactory设置对应的连接异常类
        exceptionClasses.add(ConnectException.class);
        retryableClassifier = new BinaryExceptionClassifier(exceptionClasses);
    }


    public MyRibbonLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser) {
        super(serviceId, context, loadBalanceChooser);
    }

    public MyRibbonLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser,
                                           IClientConfig clientConfig) {
        super(serviceId, context, loadBalanceChooser,
                clientConfig);
    }

    /**
     * 支持连接超时、连接拒绝的情况下,重试所有请求
     *
     * @param context
     * @return
     */
    @Override
    public boolean canRetry(LoadBalancedRetryContext context) {
        return retryForException(context.getLastThrowable()) || super.canRetry(context);
    }

    /**
     * 判断某异常能否重试
     *
     * @param ex
     * @return
     */
    private boolean retryForException(Throwable ex) {
        // SimpleClientHttpRequestFactory连接超时返回:java.net.SocketTimeoutException: connect timed out
        // 这里做特殊处理
        if (ex != null && StringUtils.contains(ex.getMessage(), CONNECT_TIMED_OUT)) {
            return true;
        }
        return retryableClassifier.classify(ex);
    }

}

3、自定义的LoadBalancedRetryPolicyFactory

/**
     * 支持连接超时重试
     *
     * @param springClientFactory
     * @return
     */
    @Bean
    public LoadBalancedRetryPolicyFactory ribbonLoadBalancedRetryPolicyFactory(SpringClientFactory springClientFactory) {
        return new LoadBalancedRetryPolicyFactory() {
            @Override
            public LoadBalancedRetryPolicy create(String serviceId, ServiceInstanceChooser loadBalanceChooser) {
                RibbonLoadBalancerContext lbContext = springClientFactory
                        .getLoadBalancerContext(serviceId);
                return new MyRibbonLoadBalancedRetryPolicy(serviceId, lbContext,
                        loadBalanceChooser, springClientFactory.getClientConfig(serviceId));
            }
        };
    }

Hystrix配置

这里只简单配置超时时间,时间最好大于上述重试的时间:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 10000

疑问解答:

  1. 我的服务配置的url是 http://serviceName/function,它是怎么知道调用到哪个IP端口去的?
    答:由RibbonLoadBalancerClient内部分3步实现:
    a:根据serviceName去服务注册中心中获取当前注册的实例(ServerList);
    b:根据负载均衡规则(IRule)选出下一个用于访问的服务;
    c:根据选中的服务构建 http://IP:port/function形式的url。
  2. 为什么请求还在往已经挂掉的服务器上发?
    答:同样要从服务注册与移除的机制、Ribbon内部的熔断机制(Hystrix在这里我们当成外部的熔断机制)以及Ping规则来回答:
    a:默认情况下服务实例通过向注册中心发送心跳请求来汇报存活状态,注册中心每间隔一段时间(60s)会移除超过指定时间(90s)没有发送心跳请求的服务实例,所以说理论上来说,最长可能有150s的时间,某实例失效了但仍然在注册中心中;
    b:默认不会启用Ribbon内部熔断,即niws.loadbalancer.availabilityFilteringRule.filterCircuitTripped:=false,即就算ribbon知道某个服务有问题(连续3次连不上),由于没有开启熔断,Ribbon也不会跳过这个实例;
    c:默认的Ping规则上面也说了,只是检查是否在注册中,并不会实际发起Ping到服务实例去;
    知道原因了,要配置快速移除相信大家也知道怎么做了。
  3. 为什么同一笔交易发了3次?
    答:检查是否开启了重试以及重试配置等,详见RetryTempalte配置章节。
  4. 我只想在连接超时时进行重试怎么做?
    答:参考上面的MyRibbonLoadBalancedRetryPolicy。
  5. 其他疑问可以在评论留言。

总结

一般来说,SpringBoot的自动配置能帮你搞定,但如果想要实现某些定制化的需求,最简单的办法就是扩展对应的代码,添加定制的功能。

代码戳这里

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

运营前线 2

运营前线 2

兰军 等著 / 机械工业出版社 / 2017-4 / 69.00

“运营前线”是一个系列,目前已经出版2部,与“产品前线”一样,该系列书也由资深的产品和运营专家兰军(Blues)领衔策划和写作,旨在梳理和总结国内一线互联网公司的运营方法和技巧,让所有产品人和运营人都有机会了解和学习这些大的互联网公司是如何做运营的。 这2部作品汇集了来自腾讯、阿里、百度、360、迅雷、YY、小米、爱奇艺、乐视等数十家大型互联网公司的一线运营专家的技巧和方法论。共包含9大运营......一起来看看 《运营前线 2》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器