高性能HttpClient

最近有很多反馈说项目卡顿,查了oracle的阻塞语句,发现很奇怪的事情,里面居然有查询语句而且是带索引的,感觉很奇怪。

oracle查询阻塞方法,这个网上都找得到,我顺便复制到这里

-- 查询连接数量
SELECT username, machine, program, status, COUNT (machine) AS
连接数量
FROM v$session
GROUP BY username, machine, program, status
ORDER BY machine;

-- 查询阻塞的sql
select l.session_id sid, 
s.serial#, 
l.locked_mode, 
l.oracle_username, 
s.user#, 
l.os_user_name, 
s.machine, 
s.terminal, 
a.sql_text, 
a.action 
from v$sqlarea a, v$session s, v$locked_object l 
where l.session_id = s.sid 
and s.prev_sql_addr = a.address 
order by sid, s.serial#;

-- (2)查看哪个表被锁
select b.owner,b.object_name,a.session_id,a.locked_mode from
v$locked_object a,dba_objects b where b.object_id = a.object_id;

--(3)查看是哪个session引起的
select a.OS_USER_NAME, c.owner, c.object_name, b.sid, b.serial#,
logon_time from v$locked_object a, v$session b, dba_objects c where
a.session_id = b.sid and a.object_id = c.object_id order by
b.logon_time;

-- 杀掉阻塞会话
alter system kill session '512,37473';

百思不得其解之时,去看了一眼报错的日志,发现大量的这种错误

连接超时,对方接口没问题,那就只能是连接资源被耗光了

netstat -ant|awk '/^tcp/ {++S[$NF]} END {for(a in S) print (a,S[a])}'

发现close_wait 数量上千,这个状态会消耗资源,可以参考一下这篇文章

https://blog.csdn.net/smallbirdnq/article/details/106426315

解决方式

先看一下之前的请求工具类的方法

public ResponseBase doGet(String url) {
        try {
            //创建一个httpGet请求
            HttpGet request = new HttpGet(url);
            //创建一个htt客户端
            HttpClient httpClient = HttpClientBuilder.create().build();
            //添加cookie到头文件
            //接受客户端发回的响应
            HttpResponse httpResponse = httpClient.execute(request);
            //获取返回状态
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            //如果是响应成功
            if (statusCode != HttpStatus.SC_OK) {
                throw new DIYException(setResultError("发送GET请求失败").toString());
            }
            //得到客户段响应的实体内容
            HttpEntity responseHttpEntity = httpResponse.getEntity();
            String string = EntityUtils.toString(responseHttpEntity, "utf-8");
            return setResultSuccessData(string, "发送GET请求成功");
        } catch (Exception e) {
            log.error("发送GET请求异常:" + e.getMessage());
            throw new DIYException(setResultError("发送GET请求异常:" + e.getMessage()).toString());
        }
    }

每次请求都会创建一个httpClient,非常浪费,也没做资源的回收。

那么我们可以考虑只创建一个httpClient,连接池的方式去管理,并且创建一个后台线程去做资源的释放与回收。

// 使用管理器,管理HTTP连接池 无效链接定期清理功能
public class HttpClientConnectionMonitorThread extends Thread{

    private final HttpClientConnectionManager connManager;
    private volatile boolean shutdown;

    public HttpClientConnectionMonitorThread(HttpClientConnectionManager connManager) {
        super();
        this.setName("http-connection-monitor");
        this.setDaemon(true);
        this.connManager = connManager;
        this.start();
    }

    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    // 等待5秒
                    wait(5000);
                    // 关闭过期的链接
                    connManager.closeExpiredConnections();
                    // 选择关闭 空闲30秒的链接
                    connManager.closeIdleConnections(30, TimeUnit.SECONDS);
                }
            }
        } catch (InterruptedException ex) {
        }
    }
}
public class HttpClientFactory {
    /** 连接池最大连接数 */
    private static final Integer MAX_TOTAL = 1200;
    /** 单个路由默认最大连接数 */
    private static final Integer MAX_PER_ROUTE = 200;
    /** 请求超时时间ms */
    private static final Integer REQ_TIMEOUT =  5 * 1000;
    /** 连接超时时间ms */
    private static final Integer CONN_TIMEOUT = 5 * 1000;
    /** 读取超时时间ms */
    private static final Integer SOCK_TIMEOUT = 25 * 1000;
    /** HTTP链接管理器线程 */
    private static HttpClientConnectionMonitorThread thread;

    public static HttpClientConnectionMonitorThread getThread() {
        return thread;
    }
    public static void setThread(HttpClientConnectionMonitorThread thread) {
        HttpClientFactory.thread = thread;
    }

    public static HttpClient createSimpleHttpClient(){
        SSLConnectionSocketFactory sf = SSLConnectionSocketFactory.getSocketFactory();
        return HttpClientBuilder.create()
                .setSSLSocketFactory(sf)
                .build();
    }

    public static HttpClient createHttpClient() {
        // 定义keepAlive
        ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
            @Override
            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                if (response == null) {
                    throw new IllegalArgumentException("HTTP response may not be null");
                }
                HeaderElementIterator it= new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while(it.hasNext()) {
                    HeaderElement he=it.nextElement();
                    String param=he.getName();
                    String value=he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        return Long.parseLong(value) * 1000;
                    }
                }
                //如果没有约定,则默认定义时长为60s
                return 60 * 1000;
            }
        };
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        poolingHttpClientConnectionManager.setMaxTotal(MAX_TOTAL);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(REQ_TIMEOUT)
                .setConnectTimeout(CONN_TIMEOUT)
                .setSocketTimeout(SOCK_TIMEOUT)
                .build();
        //管理 http连接池
        HttpClientFactory.thread=new HttpClientConnectionMonitorThread(poolingHttpClientConnectionManager);
        return HttpClients.custom()
                .setConnectionManager(poolingHttpClientConnectionManager)
                .setKeepAliveStrategy(myStrategy)
                .setDefaultRequestConfig(requestConfig)
                .build();
    }

}

前置连接池就算创建好了,接下来是使用方式

@Slf4j
@Component
public class HttpUtil {

    /**
     * 自定义请求工具类 连接池
     */
    private static final HttpClient myHttpClient = HttpClientFactory.createHttpClient();

    /**
    * GET请求
    */
    public ResponseBase doGet(String url) {
        try {
            // 通过HttpPost来发送post请求
            HttpGet httpGet = new HttpGet(url);
            CustomResponseHandler responseHandler = new CustomResponseHandler();
            String result = myHttpClient.execute(httpGet, responseHandler);
            return setResultSuccessData(result, "发送GET请求成功");
        } catch (Exception e) {
            log.error("发送GET请求异常:" + e.getMessage());
            throw new DIYException(setResultError("发送GET请求异常:" + e.getMessage()).toString());
        }
    }

    /**
    * POST请求
    */
    public ResponseBase doPost(String url, String param, String contentType){
        try {
            // 通过HttpPost来发送post请求
            HttpPost httpPost = new HttpPost(url);
            StringEntity stringEntity = new StringEntity(param, "UTF-8");
            stringEntity.setContentEncoding("UTF-8");
            stringEntity.setContentType(contentType);
            httpPost.setEntity(stringEntity);
            CustomResponseHandler responseHandler = new CustomResponseHandler();
            String result = myHttpClient.execute(httpPost, responseHandler);
            return setResultSuccessData(result, "发送POST请求成功");
        } catch (Exception e) {
            log.error("发送POST请求异常:" + e.getMessage());
            throw new DIYException(setResultError("发送POST请求异常:" + e.getMessage()).toString());
        }
    }
}

更推荐使用HttpClient.execute(httpPost, responseHandler) 响应处理器的方式,这样资源会少消耗,并且我们不用去管资源的回收

public class CustomResponseHandler extends BasicResponseHandler {
    /**
     * Returns the entity as a body as a String.
     *
     * @param entity
     */
    @Override
    public String handleEntity(HttpEntity entity) throws IOException {
        return EntityUtils.toString(entity, "UTF-8");
    }
}
有兴趣的去看下源码 CloseableHttpClient
public <T> T execute(final HttpHost target, final HttpRequest request,
            final ResponseHandler<? extends T> responseHandler, final HttpContext context)
            throws IOException, ClientProtocolException {
        Args.notNull(responseHandler, "Response handler");

        final CloseableHttpResponse response = execute(target, request, context);
        try {
            // 注意这里
            final T result = responseHandler.handleResponse(response);
            final HttpEntity entity = response.getEntity();
            EntityUtils.consume(entity);
            return result;
        } catch (final ClientProtocolException t) {
            // Try to salvage the underlying connection in case of a protocol exception
            final HttpEntity entity = response.getEntity();
            try {
                EntityUtils.consume(entity);
            } catch (final Exception t2) {
                // Log this exception. The original exception is more
                // important and will be thrown to the caller.
                this.log.warn("Error consuming content after an exception.", t2);
            }
            throw t;
        } finally {
            response.close();
        }
    }
    @Override
    public T handleResponse(final HttpResponse response)
            throws HttpResponseException, IOException {
        final StatusLine statusLine = response.getStatusLine();
        final HttpEntity entity = response.getEntity();
        if (statusLine.getStatusCode() >= 300) {
            EntityUtils.consume(entity);
            throw new HttpResponseException(statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
        }
        return entity == null ? null : handleEntity(entity);
    }

无论怎样,返回不是成功或者异常最后都会消费掉EntityUtils.consume(entity);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值