Java 业务开发常见错误 100 例
朱晔
贝壳金服资深架构师
51940 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 48 讲
代码篇 (23讲)
Java 业务开发常见错误 100 例
15
15
1.0x
00:00/00:00
登录|注册

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

你好,我是朱晔。今天,我们一起聊聊进行 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/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 业务开发常见错误 100 例》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(61)

  • 最新
  • 精选
  • 徐典阳✔️
    置顶
    朱老师,请问Feign声明式HTTP接口调用可以针对某服务单个接口配置读取超时参数吗?我们这边一个微服务有n个接口,有一些接口处理耗时长有一些处理耗时短,但调用方又不期望针对同一个微服务声明多个Feign client。我简单翻了源码没有找到。

    作者回复: 可以,补充了一个例子: https://github.com/JosephZhu1983/java-common-mistakes/blob/master/src/main/java/org/geekbang/time/commonmistakes/httpinvoke/feignpermethodtimeout/FeignPerMethodTimeoutController.java Feign比较新的版本才会支持: https://github.com/OpenFeign/feign/pull/970 相关源码: SynchronousMethodHandler Options findOptions(Object[] argv) { if (argv == null || argv.length == 0) { return this.options; } return (Options) Stream.of(argv) .filter(o -> o instanceof Options) .findFirst() .orElse(this.options); }

    2
    23
  • 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

    11
  • 蚂蚁内推+v
    置顶
    老师,我这边工作过程中遇到服务端 499 这块要怎么从链接超时和读取超时设置去分析呢?

    作者回复: 499情况比较特殊,虽然表现为服务端(一般为代理,比如nginx)记录和返回499状态码,但是其实是因为处理时间太长,客户端超时主动关闭连接,排查两点: 1、客户端读取超时时间多久 2、服务端为什么处理这么慢超过了客户端的读取超时 如果希望不要499的话,对于nginx可以开启 proxy_ignore_client_abort,这样可以让请求在服务端执行完成

    24
  • 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.

    作者回复: 👍🏻

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

    作者回复: 总结的不错

    31
  • Geek_d7ede4
    老师您好,我之前对接过一个第三方支付接口,调用支付接口a账户对b账户进行了转账操作,我业务数据库也要做一个记账操作在数据库中,如何保证调用第三方支付接口和我本地的业务是一致性的呢?就是第三方支付接口有可能已经转账成功了,但是我业务代码可能抛异常,导致回滚了。

    作者回复: 很典型的问题: 1、先创建支付订单,再提交外部,创建订单的操作独立事务,不要回滚(否则出异常了,订单都没了,补偿的依据都没有) 2、只有外部接口告诉你明确成功或失败了,你才能认为操作成功或失败 3、否则由定时任务调用外部查询接口查询交易结果,然后根据查到的结果补偿本地状态

    7
    30
  • Unravel👾
    老师您好 前段时间遇到过一个连接超时的问题,在springboot中使用restTemplate(无论是不配置还是增大超时时间或是加入apache http client连接池)在业务中请求另外一个服务的接口经常会出connect timeout(经过nginx或是直接连接tomcat都会出现) 此时ping、telnet、curl都是成功的 但是如果另有一个任务定时一直请求接口,那么在业务中就不会出现connect timeout了。 一直没有成功解决这个问题,想问下老师可以从哪方面入手,谢谢老师

    作者回复: 既然你是遇到偶尔出现连接失败,说明对端端口是开的,这种连接超时偶发问题一般是网络问题,丢包、防火墙、带宽打满、网卡配置问题,甚至是硬件问题(网线网口)等引起的,链路上任何一个环节的软件和硬件都可能引起问题,抓包分析吧。 另外ping一次是成功的,长ping一下看看。curl一次是成功的,做一个监控10s一次curl一次试试。

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

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

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

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

    4
  • 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>

    3
    3
收起评论
显示
设置
留言
61
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部