美文网首页
并发场景org.apache.http.conn.Connect

并发场景org.apache.http.conn.Connect

作者: Nifury | 来源:发表于2020-04-23 18:07 被阅读0次

起因

线上运行的公众号模板消息批量推送时会有10%左右的失败,这在测试环境下并未出现,日志定位为如下错误:

org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.leaseConnection(PoolingHttpClientConnectionManager.java:316)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$1.get(PoolingHttpClientConnectionManager.java:282)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:190)

先说结果

使用的公众号开发SDK使用HttpClient作为HTTP请求工具,并默认设置connectionRequestTimeout=3000。此问题与请求服务端无关,抛出这个异常时请求还未从客户端发出,它实际是由于大量HTTP请求需处理时连接池没有可用连接,等待超过设置时间抛出异常。虽然我们发送模板消息时使用了线程池,但线程池最大线程数(30)多于HttpClient默认maxConnPerHost连接池数量(10),所以网络请求相对较慢依然会造成请求堆积。
connectionRequestTimeout=3000参数的含义:当一个线程需要发送HTTP请求时,从连接池取一个连接,如果等待3秒还未取到可用连接,那么直接抛出异常不再发送此请求。
maxConnPerHost参数含义:默认是10,每个服务域名最多给多少个连接,一般少于总连接数
所以解决这个问题有以下几种方式可以解决

  • 更改connectionRequestTimeout参数,调到更长时间可减少此异常发生几率,或者直接设置为-1让此参数失效
  • 多线程HTTP请求时控制线程数不要超过maxConnPerHost
  • 如机器性能较号可调高maxConnPerHost参数,增加允许并发量

分析

推送模板消息实际是通过构造参数发送POST请求腾讯微信公众平台,测试环境没有进行大量并发测试(推送模板消息需要真实的粉丝用户openid,测试号没有很多粉丝)。那么是使用配置不当?网络不稳定导致请求发送不成功?还是HttpClient 并发有bug?HttpClient 是apache出品的成熟工具,发送模板消息使用的SDK已做了请求失败重试,还是先检查是不是自己使用配置的问题。

我们知道HttpClient是有配置连接池的,这个错误一看就能大概猜到是从连接池获取连接超时,可是为什么会出现这个错误?超出连接池连接数量的请求不是应该在排队等待?

经过模拟脚本模仿生产环境配置参数,并多次调整配置参数测试,定位问题为connectionRequestTimeout参数配置不当。

模拟测试

模拟环境如下

  • JDK1.8
  • HttpClient 4.5.11

模拟代码如下

import org.apache.http.Consts;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class Test {
    static CloseableHttpClient httpClient = HttpClients.createDefault();
    static ExecutorService excutor = Executors.newFixedThreadPool(30);
    static RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(100).build();//设置获取连接超时时间,为重现问题这里故意设置比较小
    static final String TEST_URL = "http://pv.sohu.com/cityjson";//测试链接,这里使用搜狐的开放IP查询接口

    public static void main(String[] args) throws InterruptedException {
        final int testCount = 500;//任务重复次数
        AtomicInteger successCount = new AtomicInteger(0);//请求成功数量
        CountDownLatch latch  = new CountDownLatch(testCount);//用于判断线程池中任务是否全部执行完毕
        Long time1 = System.currentTimeMillis();
        for (int i = 0; i < testCount; i++) {
            excutor.submit(()->{
                String res = httpPost(TEST_URL,null);
                System.out.println(res);
                if(null!=res && !res.isEmpty())successCount.addAndGet(1);
                latch.countDown();
            });
        }
        latch.await();//等待线程池中的线程全部执行完
        Long time2 = System.currentTimeMillis();
        System.out.println("耗时:"+(time2-time1)+"毫秒,成功:"+successCount.get());
        excutor.shutdown();
    }
    public static String httpPost(String uri,String data){
        HttpPost  post = new HttpPost(uri);
        post.setConfig(requestConfig);
        if(data!=null){
            StringEntity entity = new StringEntity(data, Consts.UTF_8);
            post.setEntity(entity);
        }
        CloseableHttpResponse response=null;
        try {
            response = httpClient.execute(post);
            int statusCode = response.getStatusLine().getStatusCode();
            return new BasicResponseHandler().handleResponse(response);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            post.releaseConnection();
        }
        return null;
    }
}

测试结果,500个请求成功27个😂,大量的ConnectionPoolTimeoutException

耗时:862毫秒,成功:27

调整参数测试

static RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(1000).build();//设置请求连接超时时间
  • 请求连接超时时间:1000 (1秒)

    耗时:9459毫秒,成功:420

  • 请求连接超时时间:3000 (3秒)

    耗时:11093毫秒,成功:500

说明这个模拟测试脚本里面,请求连接超时时间设置到3S可以达到100%成功,但是生产环境对发送成功率要求很高,设置3S超时合适吗?所以再看看HttpClient源码来找找答案:

//获取连接关键代码,有精简:org.apache.http.impl.conn.PoolingHttpClientConnectionManager#leaseConnection
protected HttpClientConnection leaseConnection(Future<CPoolEntry> future, long timeout, TimeUnit timeUnit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
        try {
            CPoolEntry entry = (CPoolEntry)future.get(timeout, timeUnit);
            if (entry != null && !future.isCancelled()) {
                Asserts.check(entry.getConnection() != null, "Pool entry with no connection");
                //...
                return CPoolProxy.newProxy(entry);
            } else {
                throw new ExecutionException(new CancellationException("Operation cancelled"));
            }
        } catch (TimeoutException var7) {
            throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
        }
    }

其中连接从Future<CPoolEntry>中获取,其默认connectionRequestTimeout=-1,也就是永不过期,生产环境要求请求一定要成功,所以设置一直等待获取连接即可!

配置参考

connectionRequestTimout:指从连接池获取连接的timeout
connetionTimeout:指客户端和服务器建立连接的timeout,就是http请求的三个阶段,一:建立连接;二:数据传送;三,断开连接。超时后会ConnectionTimeOutException
socketTimeout:指客户端从服务器读取数据的timeout,超出后会抛出SocketTimeOutException

相关文章

  • 并发场景org.apache.http.conn.Connect

    起因 线上运行的公众号模板消息批量推送时会有10%左右的失败,这在测试环境下并未出现,日志定位为如下错误: 先说结...

  • 对常见高并发一些业务场景的理解

    常见并发场景,本人可以认为分为,并发读,和并发写的主要场景,并发读,可以使用缓存,并发写使用的技术就比较多了。 案...

  • 01-并发概述

    并发编程由来: 串行与并行 并发编程目的 并发编程的场景

  • [JPT_06]性能测试-场景设计与实现(登录&随机购买

    目录结构 一、用户登录并发基准场景设置 对于并发场景,本次测试目的在于验证ECShop平台能否支持100个用户并发...

  • 高并发高可用系统以及面试分析

    1.高并发,高可用系统的一些思考 高并发依赖于场景和逻辑 不一定每个场景都会产生高并发,不要为了高并发而盲目的设计...

  • ReadWriteLock

    ReadWriteLock 适用于读多写少的场景,针对读多写少这种并发场景,Java SDK并发包提供了读写锁——...

  • Java中Synchornize关键字原理

    在实际开发中,往往需要考虑数据并发安全问题,比如秒杀业务场景、买票业务场景,都需要考虑并发,Java提供了Sync...

  • 并发的解决场景

    1.高并发场景的解决方案1.1 扩容1.2 缓存(Redies Memache)1.3 消息队列 (kafak M...

  • 并发场景下死锁

    案例场景 例如账户A 转账户B、账户C 转账户D这两个转账操作。 这种方式采用了细粒度锁。使用细粒度锁可以提高并行...

  • Java高性能高并发秒杀系统设计与优化

    项目简介 本项目主要是模拟应对大并发场景下,如何完成商品的秒杀,以及针对秒杀场景下为应对大并发所做的优化。 项目地...

网友评论

      本文标题:并发场景org.apache.http.conn.Connect

      本文链接:https://www.haomeiwen.com/subject/afbaihtx.html