Java 业务开发常见错误 100 例
朱晔
贝壳金服资深架构师
立即订阅
4289 人已学习
课程目录
已更新 10 讲 / 共 37 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 业务代码真的会有这么多坑?
免费
代码篇 (7讲)
01 | 使用了并发工具类库,线程安全就高枕无忧了吗?
02 | 代码加锁:不要让“锁”事成为烦心事
03 | 线程池:业务代码最常用也最容易犯错的组件
04 | 连接池:别让连接池帮了倒忙
05 | HTTP调用:你考虑到超时、重试、并发了吗?
06 | 20%的业务代码的Spring声明式事务,可能都没处理正确
07 | 数据库索引:索引并不是万能药
不定期加餐 (2讲)
加餐1 | 带你吃透课程中Java 8的那些重要知识点(上)
加餐2 | 带你吃透课程中Java 8的那些重要知识点(下)
Java 业务开发常见错误 100 例
登录|注册

05 | HTTP调用:你考虑到超时、重试、并发了吗?

朱晔 2020-03-19
你好,我是朱晔。今天,我们一起聊聊进行 HTTP 调用需要注意的超时、重试、并发等问题。
与执行本地方法不同,进行 HTTP 调用本质上是通过 HTTP 协议进行一次网络请求。网络请求必然有超时的可能性,因此我们必须考虑到这三点:
首先,框架设置的默认超时是否合理;
其次,考虑到网络的不稳定,超时后的请求重试是一个不错的选择,但需要考虑服务端接口的幂等性设计是否允许我们重试;
最后,需要考虑框架是否会像浏览器那样限制并发连接数,以免在服务并发很大的情况下,HTTP 调用的并发数限制成为瓶颈。
Spring Cloud 是 Java 微服务架构的代表性框架。如果使用 Spring Cloud 进行微服务开发,就会使用 Feign 进行声明式的服务调用。如果不使用 Spring Cloud,而直接使用 Spring Boot 进行微服务开发的话,可能会直接使用 Java 中最常用的 HTTP 客户端 Apache HttpClient 进行服务调用。
接下来,我们就看看使用 Feign 和 Apache HttpClient 进行 HTTP 接口调用时,可能会遇到的超时、重试和并发方面的坑。

配置连接超时和读取超时参数的学问

对于 HTTP 调用,虽然应用层走的是 HTTP 协议,但网络层面始终是 TCP/IP 协议。TCP/IP 是面向连接的协议,在传输数据之前需要建立连接。几乎所有的网络框架都会提供这么两个超时参数:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java 业务开发常见错误 100 例》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(25)

  • Monday 置顶
    我们来分析一下源码。打开 RibbonClientConfiguration 类后,会看到 DefaultClientConfigImpl 被创建出来之后,ReadTimeout 和 ConnectTimeout 被设置为 1s:

    /**
     * Ribbon client default connect timeout.
     */
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

    /**
     * Ribbon client default read timeout.
     */
    public static final int DEFAULT_READ_TIMEOUT = 1000;

    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
       DefaultClientConfigImpl config = new DefaultClientConfigImpl(); //此行打断点
       config.loadProperties(this.name);
       config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
       config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
       config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
       return config;
    }

    被死扣的毛病折腾着,以上这段描述和代码中,有两个疑问,烦老师解惑,谢谢。
    1、使用默认配置,我在标注行打了断点,debug启动时未进断点。是不是表明默认值不是在此段代码设置的?
    2、找到了feign配置的原始类FeignClientProperties,但是没找到ribbon的。

    作者回复: 1、启动时不进断点不代表不是,执行后会进断点,原因是LoadBalancerFeignClient.execute(),运行时注入依赖的,这个方法一路追下去:

    IClientConfig getClientConfig(Request.Options options, String clientName) {
    IClientConfig requestConfig;
    if (options == DEFAULT_OPTIONS) {
    requestConfig = this.clientFactory.getClientConfig(clientName);
    }
    else {
    requestConfig = new FeignOptionsClientConfig(options);
    }
    return requestConfig;
    }


    2、ribbon是netflix的三方库,不是spring boot @ConfigurationProperties玩法,Key定义在:

    com.netflix.client.config.CommonClientConfigKey

    2020-03-20
    4
  • Darren 置顶
    试着回答下问题:
    1、为什么很少见到写入超时,客户端发送数据到服务端,首先接力连接(TCP),然后写入TCP缓冲区,TCP缓冲区根据时间窗口,发送数据到服务端,因此写入操作可以任务是自己本地的操作,本地操作是不需要什么超时时间的,如果真的有什么异常,那也是连接(TCP)不上,或者超时的问题,连接超时和读取超时就能覆盖这种场景。
    2、proxy_next_upstream:语法: proxy_next_upstream
          [error|timeout|invalid_header|http_500|http_503|http_404|off]
          默认值: proxy_next_upstream error timeout
          即 error timeout会自动重试
    可以修改默认值,在去掉error和timeout,这样在发生错误和超时时,不会重试
    proxy_next_upstream_tries 这个参数决定重试的次数,0表示关闭该参数
    Limits the number of possible tries for passing a request to the next server. The 0 value turns off this limitation.

    作者回复: 👍🏻

    2020-03-19
    4
  • 小美 置顶
    老师,我这边工作过程中遇到服务端 499 这块要怎么从链接超时和读取超时设置去分析呢?

    作者回复: 499情况比较特殊,虽然表现为服务端(一般为代理,比如nginx)记录和返回499状态码,但是其实是因为处理时间太长,客户端超时主动关闭连接,排查两点:
    1、客户端读取超时时间多久
    2、服务端为什么处理这么慢超过了客户端的读取超时

    如果希望不要499的话,对于nginx可以开启
    proxy_ignore_client_abort,这样可以让请求在服务端执行完成

    2020-03-19
    1
  • 👽
    这已经不单单是一个坑了,而是N一个场景下,多种多样的坑。
    Spring Boot 带来了【约定大于配置】的说法,但是,本文告诉我们,越是约定大于配置,越是要对那些“默认配置”心里有数才行。
    HTTP请求,说到底,还是网络调用。某个老师曾说过,网络,就是不靠谱的。就存在拥塞,丢包等各种情况。从而使得排查的难度更大。要考虑的角度,宽度,都更广。不单是客户端,服务端,甚至还要考虑网络环境。这对程序员具备的技术深度,广度都有了更高的要求。
    今天的收货:
    首先,增长了经验。知道了有这么些坑,虽然不一定能记得住,最起码留个印象。以后碰到类似的问题了能想起来。
    然后,不能盲目相信默认配置。条件允许的情况下,还是需要了解关注那些默认配置以及默认实现。
    最后,对HTTP调用,的测试方式与模拟方式,也了解到了测试方式。如何分别设置超时时间来找问题。

    其实,还希望能听听老师讲讲HTTP调用出问题的排查思路与方案。

    作者回复: 总结的不错

    2020-03-19
    6
  • Monday
    花了两个晚上终于还是把这节啃了下来,准备运行环境,重现所有问题,翻看相关源码。
    终于等到你,还好我没放弃。
    个人感悟,这些坑对以后快速排查问题,肯定有帮助。就算以后淡忘了这节的内容,但至少还会有些许记忆的,哪个专栏,哪个老师,哪篇文章。感谢老师!

    作者回复: 如果觉得有用可以多转发分享

    2020-03-20
    2
  • 一个想偷懒的程序坑
    虽然没处理过这块儿的东西,但看完了解了许多知识,赞!

    作者回复: 如果觉得有用可以多转发分享

    2020-03-20
    2
  • Monday
    好文章,好“坑”。

    作者回复: 如果觉得好,可以多分享转发

    2020-03-19
    2
  • justinzhong
    老师,针对发送短信的那个例子,解决重试问题的方法一:就是get请求换成post请求,我试了几次都是不行的,还是会重试一次,但是方法二是完全可以的。可以针对方法一的解决重试问题的思路再描述的清楚一些吗?

    作者回复: 1. 确定没看错,也就是RibbonRetryIssueServerController只看到一次输出
    2. 确定参数ribbon.OkToRetryOnAllOperations没有设置为true

    2020-03-21
    1
  • 斐波那契
    里面说的坑也许过了一段时间就忘了 当时有四个字是我学到的 那就是“查看源码”
    2020-03-20
    1
  • 陈天柱
    之前用Spring Cloud就遇到过feign调用超时的坑,始终配置readTimeout值都不生效。虽然后面网上查阅了资料暂时性解决了,但是看了老师的解决问题思路才发现,这个时候就需要带着问题去阅读源码找寻答案,提高自己阅读源码的能力。
    2020-03-20
    1
  • 汝林外史
    sendRequest(int count, Supplier<CloseableHttpClient> client) 这个方法中第二个参数为什么要用一个函数接口而不是直接用CloseableHttpClient类型呢? 我看也没用到什么特性,只是调用了execute方法而已?
    课后问题:1. 感觉写入超时已经包含在读取超时这个里面,没必要单独定义这么细的超时。
    2. nginx真不是很了解,老师想加餐了吗?哈哈

    作者回复: 这里是可以直接传CloseableHttpClient的

    2020-03-20
    1
  • Alpha
    非常同意选择Get还是Post应该依据API的行为。
    但是有时数据查询的API参数确实不得已很长,会导致浏览器的长度限制,老师有好的办法吗?

    作者回复: 这么长的参数看看是否合理,对于有一些数据它可能并不是查询参数可以放头里传

    2020-03-20
    1
  • Monday
    public class ClientReadTimeoutController {
        private String getResponse(String url, int connectTimeout, int readTimeout) throws IOException {
            return Request.Get("http://localhost:45678/clientreadtimeout" + url)
                    .connectTimeout(connectTimeout)
                    .socketTimeout(readTimeout)
                    .execute()
                    .returnContent()
                    .asString();
        }
    ....
    }


    这第一段代码中Request这个类,是引用哪个包下的?找得好辛苦,老师第5节的代码也没上传到git

    作者回复: 源码里面有,在clientreadtimeout里
    Request是在这里:
    <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>fluent-hc</artifactId>
                <version>4.5.9</version>
            </dependency>

    2020-03-19
    1
    1
  • 终结者999号
    老师,对于Http Client和Ok Http相比,是不是OkHttp支持得更好,而且HTTP2相比于HTTP1.1的新特性是不是也使得我们不用过去的一些配置了啊

    作者回复: 我个人觉得okhttp易用性更高一点,不过okhttp应该在安卓领域更火一点,后端使用okhttp的应该不多。万变不离其宗,使用任何httpclient都要考虑连接池、超时配置、自动重试和并发问题

    2020-03-19
    1
  • 大尾巴老猫
    void server();
    这一句什么意思?

    作者回复: 就是模拟一个服务端接口

    2020-03-19
    1
  • Monday
    ribbon.ReadTimeout=4000
    ribbon.ConnectTimeout=4000

    这个参数的key命名不规范,是有故事,还是开发人员不够专业?

    作者回复: 这就不清楚了

    2020-03-19
    1
  • 小美
    老师我是做客户端的 ,我们这边还有个写超时概念这块老师方便分享下不

    作者回复: 其实也就是到socket sendbuffer的超时(满的话等待空间释放的超时)

    2020-03-19
    1
  • 公号-云原生程序员
    老师总结的很有深度、很全面、很有业务实战

    作者回复: 如果觉得好,可以多分享

    2020-03-19
    1
  • pedro
    对于数据写入,开发者都可以直接控制,要么先write然后再一次性flush,要么边write边flush,至于最后socket缓冲区中的数据如何发送,都交给了tcp。

    作者回复: 嗯大概意思对 可以再搜一下相关资料继续研究一下

    2020-03-19
    1
  • 梦倚栏杆
    按照老师解释的读取超时的概念:字节流放入socket--->服务端处理------->服务端返回--->取出字节流。
    那写入超时估计就是字节流放入socket的时间,这个属于自己主动控制的可能没有必要吧,具体可能还需要了解一下网络编程才能知道。

    作者回复: 嗯大概意思对 可以再搜一下相关资料继续研究一下

    2020-03-19
    1
收起评论
25
返回
顶部