连接池相关参数配置介绍, 常见问题解决

        最近几天在测境碰到一个问题,httpclient 在使用线程池时, 间隔性的出现 NoHttpResponseException 异常。

​​​​​​​httpclient org.apache.http.NoHttpResponseException: host:443 failed to respond

用了连接池很多年了, 一搜自己的博客, 竟然没做过一次整理和收藏, 其实大致原因也猜出个八九不离十, 秉承着严谨的态度😄, 还是百度了一下...大致总结出2个原因


1.当服务端由于负载过大等情况发生时,可能会导致在收到请求后无法处理(比如没有足够的线程资源),会直接丢弃链接而不进行处理。此时客户端就会报错:NoHttpResponseException。
解决建议: 重试

2.客户端与服务端建立的请求在服务端已经失效。(例如:服务端 springboot 内置 tomcat 默认 keepAliveTimeout :20s,客户端自定义 keepAliveTimeout :30s,客户端连接池中取出的空闲连接可能已经被服务端失效,再次从连接池拿该失效连接进行请求时,就会报错。)
解决建议:检查并关闭失效连接

问题依然解决, 解决过程中温习的知识还是需要记载下, 方便下次出现问题, 或者自己再次使用和有需要的小伙伴方便查找,

PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
			// pool max connect
			pool.setMaxTotal(maxTotal);
			// 设置最大路由
			pool.setDefaultMaxPerRoute(defaultMaxPerRoute);
			
			RequestConfig requestConfig = RequestConfig.custom()
					.setConnectionRequestTimeout(connectionRequestTimeout)
					.setSocketTimeout(socketTimeout)
					.setConnectTimeout(connectTimeout)
					.build();

HttpClientUtil.closeableHttpClient = HttpClients.custom()
					// 设置连接池管理
					.setConnectionManager(pool)
					// 设置请求配置
					.setDefaultRequestConfig(requestConfig)
                    //问题一解决方案:设置重试
					.setServiceUnavailableRetryStrategy(new DefaultServiceUnavailableRetryStrategy(3, 2000))
                    //问题二解决方案:调整 keepAliveTimeout,这样无法复用长连接
					.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
					//问题二解决方案:设置重试次数
					.setRetryHandler(new DefaultHttpRequestRetryHandler(3, false))
                    //问题二解决方案:设置自动关闭过期链接
					.evictIdleConnections(30, TimeUnit.SECONDS)
					.build();

对于问题二解决方案中evictIdleConnections方法的工作原理感兴趣的同学, 可以查看源码, 这里贴出部分代码, 供参考, 其实自己实现也一样

  • 初始化变量 HttpClientBuilder.evictIdleConnections()
public final HttpClientBuilder evictIdleConnections(final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {
        this.evictIdleConnections = true;
        this.maxIdleTime = maxIdleTime;
        this.maxIdleTimeUnit = maxIdleTimeUnit;
        return this;
    }
  • 构建逻辑 HttpClientBuilder.build() 
if (evictExpiredConnections || evictIdleConnections) {
                final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,
                        maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS,
                        maxIdleTime, maxIdleTimeUnit);
                closeablesCopy.add(new Closeable() {

                    @Override
                    public void close() throws IOException {
                        connectionEvictor.shutdown();
                        try {
                            connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS);
                        } catch (final InterruptedException interrupted) {
                            Thread.currentThread().interrupt();
                        }
                    }

                });
                connectionEvictor.start();
            }
  •  初始化链接处理线程 IdleConnectionEvictor
 public IdleConnectionEvictor(
            final HttpClientConnectionManager connectionManager,
            final ThreadFactory threadFactory,
            final long sleepTime, final TimeUnit sleepTimeUnit,
            final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {
        this.connectionManager = Args.notNull(connectionManager, "Connection manager");
        this.threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory();
        this.sleepTimeMs = sleepTimeUnit != null ? sleepTimeUnit.toMillis(sleepTime) : sleepTime;
        this.maxIdleTimeMs = maxIdleTimeUnit != null ? maxIdleTimeUnit.toMillis(maxIdleTime) : maxIdleTime;
        this.thread = this.threadFactory.newThread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (!Thread.currentThread().isInterrupted()) {
                        Thread.sleep(sleepTimeMs);
                        connectionManager.closeExpiredConnections();
                        if (maxIdleTimeMs > 0) {
                            connectionManager.closeIdleConnections(maxIdleTimeMs, TimeUnit.MILLISECONDS);
                        }
                    }
                } catch (final Exception ex) {
                    exception = ex;
                }

            }
        });
    }

3.上线不久, 线上又出现了偶发502 Bad Gateway异常, 运维找了很久也没发现请求包有啥问题, 连接池各种参数修改也无济于事, 执行一段时间, 就会出现一个, 只好放大招利用重试解决 !

解决方案: 自定义ServiceUnavailableRetryStrategy

查看默认实现源码

@Override
public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) {
     return executionCount <= maxRetries &&
            response.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE;
}

可以发先, 重试策略很简单, 重试次数小于指定次数, 且返回值是503, 这里我们只需加个返回值502即可, 最后修改代码如下

/**
 * 自定义服务重试策略
 * @ClassName: MyServiceUnavailableRetryStrategy 
 * @author wangqinghua
 * @date 2024年1月21日 下午8:19:54
 */
@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class MyServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy {

    private final int maxRetries;
    private final long retryInterval;
    private final List<Integer> statusCodes;

    public MyServiceUnavailableRetryStrategy() {
        this(3, 2000, Arrays.asList(HttpStatus.SC_SERVICE_UNAVAILABLE));
    }
    
    public MyServiceUnavailableRetryStrategy(final int maxRetries, final int retryInterval, final List<Integer> statusCodes) {
        super();
        Args.positive(maxRetries, "Max retries");
        Args.positive(retryInterval, "Retry interval");
        Args.notEmpty(statusCodes, "StatusCodes not empty");
        this.maxRetries = maxRetries;
        this.retryInterval = retryInterval;
        this.statusCodes = statusCodes;
    }

    @Override
    public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) {
        return executionCount <= maxRetries && statusCodes.contains(response.getStatusLine().getStatusCode());
    }

    @Override
    public long getRetryInterval() {
        return retryInterval;
    }
}

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Hyperf v2.1.4更新日志修复 #3165 修复方法 HyperfDatabaseSchemaMySqlBuilder::getColumnListing 在 MySQL 8.0 版本中无法正常使用的问题。 #3174 修复 hyperf/database 组件中 where 语句因为不严谨的代码编写,导致被绑定参数会被恶意替换的问题。 #3179 修复 json-rpc 客户端因对端服务重启,导致接收数据一直异常的问题。 #3189 修复 kafka 在集群模式下无法正常使用的问题。 #3191 修复 json-rpc 客户端因对端服务重启,导致连接池中的连接全部失效,新的请求进来时,首次使用皆会报错的问题。 新增 #3170 为 hyperf/watcher 组件新增了更加友好的驱动器 FindNewerDriver,支持 Mac Linux 和 Docker。 #3195 为 JsonRpcPoolTransporter 新增了重试机制, 当连接、发包、收包失败时,默认重试 2 次,收包超时不进行重试。 优化 #3169 优化了 ErrorExceptionHandler 中与 set_error_handler 相关的入参代码, 解决静态检测因入参不匹配导致报错的问题。 #3191 优化了 hyperf/json-rpc 组件, 当连接中断后,会先尝试重连。 变更 #3174 严格检查 hyperf/database 组件中 where 语句绑定参数。 新组件孵化 DAG 轻量级有向无环图任务编排库。 RPN 逆波兰表示法。Hyperf简介Hyperf 是基于 Swoole 4.4+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是 可替换 与 可复用 的。框架组件库除了常见的协程版的 MySQL 客户端、Redis 客户端,还为您准备了协程版的 Eloquent ORM、WebSocket 服务端及客户端、JSON RPC 服务端及客户端、GRPC 服务端及客户端、OpenTracing(Zipkin, Jaeger) 客户端、Guzzle HTTP 客户端、Elasticsearch 客户端、Consul 客户端、ETCD 客户端、AMQP 组件、Nats 组件、Apollo、ETCD、Zookeeper 和阿里云 ACM 的配置中心、基于令牌桶算法的限流器、通用连接池、熔断器、Swagger 文档生成、Swoole Tracker、Blade、Smarty、Twig、Plates 和 ThinkTemplate 视图引擎、Snowflake 全局ID生成器、Prometheus 监控 等组件,省去了自己实现对应协程版本的麻烦。Hyperf 还提供了 基于 PSR-11 的依赖注入容器、注解、AOP 面向切面编程、基于 PSR-15 的中间件、自定义进程、基于 PSR-14 的事件管理器、Redis/RabbitMQ 消息队列、自动模型缓存、基于 PSR-16 的缓存、Crontab 秒级定时任务、Session、i18n 国际化、Validation 表单验证 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。Hyperf 功能特点框架初衷 尽管现在基于 PHP 语言开发的框架处于一个百花争鸣的时代,但仍旧未能看到一个优雅的设计与超高性能的共存的完美框架,亦没有看到一个真正为 PHP 微服务铺路的框架,此为 Hyperf 及其团队成员的初衷,我们将持续投入并为此付出努力,也欢迎你加入我们参与开源建设。设计理念 Hyperspeed + Flexibility = Hyperf,从名字上我们就将 超高速 和 灵活性 作为 Hyperf 的基因。对于超高速,我们基于 Swoole 协程并在框架设计上进行大量的优化以确保超高性能的输出。 对于灵活性,我们基于 Hyperf 强大的依赖注入组件,组件均基于 PSR 标准 的契约和由 Hyperf 定义的契约实现,达到框架内的绝大部分的组件或类都是可替换的。 基于以上的特点,Hyperf 将存在丰富的可能性,如实现 单体 Web 服务,API 服务,网关服务,分布式中间件,微服务架构,游戏服务器,物联网(IOT)等。文档齐全 我们投入了大量的时间用于文档的建设以提供高质量的文档体验,以解决各种因为文档缺失所带来的问题,文档上也提供了大量的示例,对新手同样友好。 Hyperf 官方开发文档生产可用 我们为组

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一屁小肥咩

您的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值