Ribbon——超时与重试


原文: Ribbon——超时与重试
date: 2019-04-26 18:57:04


[TOC]

前言

在上篇源码分析——客户端负载Netflix Ribbon中提到了重试, 在Spring Cloud中各组件关于重试的概念还是很容易混淆

你会发现在Netflix OSS中: Eureka, Ribbon, Zuul, feign, etc... 虽然每个功能组件都很清晰, 能够单独使用

在Spring Cloud中看似也很清晰, 但实际上Spring Cloud的整合中会把多个组件配套组合起来, 以达到简化分布式开发的目的

例如在深入实践组件Feign时, 你是不是需要了解feign-hystrix, ribbon相关的东西呢?

再例如超时, Zuul, Feign, Hystrix还是RestTemplate, 它们都有timeout相关的概念, retry机制也类似...

还好关于各组件中的超时和重试, 已经有人对其做了总结, 附上原文链接:

_感谢这些分享的人


补充

由于原文中的总结比较简单, 这里再对重试做一些补充

spring-retry

在Ribbon的重试中, 除了配置项, 还需要加入spring-retry

Retrying Failed Requests
Spring Cloud Netflix offers a variety of ways to make HTTP requests. You can use a load balanced RestTemplate, Ribbon, or Feign. No matter how you choose to create your HTTP requests, there is always a chance that a request may fail. When a request fails, you may want to have the request be retried automatically. To do so when using Sping Cloud Netflix, you need to include Spring Retry on your application’s classpath. When Spring Retry is present, load-balanced RestTemplates, Feign, and Zuul automatically retry any failed requests (assuming your configuration allows doing so).

  • pom依赖
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>


配置项 (int)

文中提及了三个配置项参数:

ribbon:
  # 同一实例最大重试次数,不包括首次调用
  MaxAutoRetries: 1
  # 重试其他实例的最大重试次数,不包括首次所选的server
  MaxAutoRetriesNextServer: 2
  # 是否所有操作都进行重试
  OkToRetryOnAllOperations: true

在ribbon-core中可以找到对应的默认值

public class DefaultClientConfigImpl implements IClientConfig {
    
    // MaxAutoRetriesNextServer (retryNextServer:重试下一实例)
    public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1; 

    // MaxAutoRetries (retrySameServer:重试相同实例)
    public static final int DEFAULT_MAX_AUTO_RETRIES = 0;
    
    // OkToRetryOnAllOperations
    public static final Boolean DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS = Boolean.FALSE;
}

这里要理解三个参数的意义, 可以这样推算出:

  • 当MaxAutoRetries=1, MaxAutoRetriesNextServer=2时: RetryCount = (1+1) * (2+1) = 6次
  • 当MaxAutoRetries=0, MaxAutoRetriesNextServer=1时: RetryCount = (0+1) * (1+1) = 2次

也就是: RetryCount = (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1)


配置项 (bool)

上面三个参数中还有一个bool参数: OkToRetryOnAllOperations

文中的解释是是否所有操作都进行重试, 这里的操作是指什么? 我第一眼看不懂

细节尽在源码中:

@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
        RibbonRequest request, IClientConfig requestConfig) {
    if (this.ribbon.isOkToRetryOnAllOperations()) {
        return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
                requestConfig);
    }
    if (!request.toRequest().method().equals("GET")) {
        return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
                requestConfig);
    }
    else {
        return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
                requestConfig);
    }
}

这是源码中的一段逻辑, 可以看到isOkToRetryOnAllOperations在这里进行了两个分支判断

  1. 判断isOkToRetryOnAllOperations=true时, 返回RetryHandler实现
  2. 判断HTTP Method
    • GET: 返回RetryHandler实现
    • !GET: 返回RetryHandler实现, 不一样的是其中二个bool值为False

可以看到, 它们都会返回一个RetryHandler实现, 不同处在于传入的参数, 更准确的说是第二个bool值参数

来看下RequestSpecificRetryHandler构造函数中的几个参数

  • bool okToRetryOnConnectErrors: 字面意思是重试连接错误, 都为true
  • bool okToRetryOnAllErrors: 字面意思是重试所有错误, 只有HTTP Method不是GET时为false
  • etc...

理解这两个参数, 你只需要能够区分两种常见的Exception, 即:

  • java.net.SocketTimeoutException: Read timed out: 这里的超时代表服务器请求超时
  • java.net.ConnectException: Connection refused: 连接被拒绝, 也就是无法连接到服务器

最大的区别在于SocketTimeoutException是服务端已经接收到请求, 而客户端没有正常接收(例如超时了)

需要明白, 这里的重试是指目标服务不可达或无法正常响应, 而不是指返回一个4xx, 5xx的错误重试

注意: OkToRetryOnAllOperations那里的HTTP Method判断作用的是服务提供者的HTTP方法, 并非调用方本身


关于Open Feign

文中提到了Feign的重试中, 虽然Feign和Ribbon充实是独立的, 如果都启用可能会让重试次数叠加

所以Spring Cloud在后来版本改为feign.Retryer#NEVER_RETRY, 即默认不开启Feign的重试, 转而可以使用Ribbon的重试配置即可

但是, 注意过Spring Cloud Finchley版的可能知道Feign的引入出现了变化, 变成了open-feign

重点有以下几点

  • OpenFeign有自己的重试机制
  • spring-retry对于OpenFeign是不起作用的
  • 但是OpenFeign中读取的配置还是ribbon.MaxAutoRetriesribbon.MaxAutoRetriesNextServer, 依然生效

详细及源码分析可参考: Spring Cloud Finchley OpenFeign的重试配置相关的坑


使用建议

重试确实能提高服务的容错和可用性, 但是使用不当不如不要使用

1: Hystrix超时时间

Hystrix的超时时间必须大于超时的时间: 这点很容易理解, 如果小于超时时间, 那就熔断了, 没有机会重试了

2: 考虑服务幂等性

这点在判断OkToRetryOnAllOperations逻辑的源码那里也能看出来, 在启用重试时一定要考虑下游服务接口的幂等性

概述为以下两点:

  • 如果你的服务API没有完全考虑幂等性, 建议不要把OkToRetryOnAllOperations设置为True
  • 其次, 即使设置为False, 如果下游服务的GET方法涉及资源操作, 无法满足幂等, 那么不建议启用重试机制
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容