HttpClient连接池小记

**欢迎关注公众号**
**微信扫一扫**

【仅需9.9订阅专栏合集,作者所有付费文章都能看(持续更新)】

推荐【Kafka教程https://bigbird.blog.csdn.net/article/details/108770504
推荐【rabbitmq教程https://bigbird.blog.csdn.net/article/details/81436980
推荐【Flink教程https://blog.csdn.net/hellozpc/article/details/109413465
推荐【JVM面试与调优教程https://bigbird.blog.csdn.net/article/details/113888604
推荐【SpringBoot全套教程https://blog.csdn.net/hellozpc/article/details/107095951
推荐【SpringCloud教程https://blog.csdn.net/hellozpc/article/details/83692496
推荐【Mybatis教程https://blog.csdn.net/hellozpc/article/details/80878563
推荐【SnowFlake教程https://blog.csdn.net/hellozpc/article/details/108248227
推荐【并发限流教程https://blog.csdn.net/hellozpc/article/details/107582771
推荐【Redis教程https://bigbird.blog.csdn.net/article/details/81267030
推荐【Netty教程https://blog.csdn.net/hellozpc/category_10945233.html

推荐Springboot集成http连接池
https://blog.csdn.net/hellozpc/article/details/106861972

  • 常用的http客户端有JDK原生的URLConnection、Netty的异步HTTP Client、Spring的RestTemplate、Spring Cloud中的Feign。
    虽然RestTemplate、Feign使用极其方便,但是屏蔽了太多底层细节,不利于全局把控。

  • 本文主要记载一下基于Apache HttpClient 的http 连接池处理。网上很多文章都没有关注定期检测关闭无效连接这块功能。另外要注册jvm钩子在程序退出时关闭httpClient实例。

/**
 * 业务层调用HttpTemplate#execute发送http post请求
 * @author zhoupengcheng
 */
public class HttpTemplate {

    private static Logger logger = LoggerFactory.getLogger(HttpTemplate.class);

    private static final String DEFAULT_ACCEPTTYPE = "application/json;charset=utf-8";
    private static final String DEFAULT_CHARSET = "utf-8";

    /**
     * 最大连接数
     */
    private static final int MAX_CONNECTION_NUM = 20;

    /**
     * 单路由最大连接数
     */
    private static final int MAX_PER_ROUTE = 20;

    /**
     * 连接超时时间,缺省为3秒钟
     */
    private static final int DEFAULT_CONNECTION_TIMEOUT = 3000;

    /**
     * 读取超时时间,缺省为3秒钟
     */
    private static final int DEFAULT_READ_TIMEOUT = 3000;

    /**
     * 连接池管理对象      
     */
    private PoolingHttpClientConnectionManager cm;

    private CloseableHttpClient httpClient;

    /**
     * 反序列化工具
     */
    private ObjectMapper objectMapper;

    public HttpTemplate() {
        init();
    }

    public void init() {
        initObjectMapper();
        initHttpClient();
    }

    private void initHttpClient() {
        HttpClientBuilder builder = HttpClients.custom();
        builder.disableCookieManagement();
        builder.disableRedirectHandling();
        builder.disableAutomaticRetries();

        builder.setConnectionManagerShared(false)
                .evictIdleConnections(1, TimeUnit.MINUTES)// 定期回收空闲连接
                .evictExpiredConnections()// 定期回收过期连接
                .setConnectionTimeToLive(1, TimeUnit.MINUTES) // 连接存活时间,如果不设置,则根据长连接信息决定
                .setDefaultRequestConfig(
                        RequestConfig
                                .custom()
                                .setAuthenticationEnabled(false)
                                .setCircularRedirectsAllowed(false)
                                .setSocketTimeout(DEFAULT_READ_TIMEOUT)
                                .setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT)
                                .setConnectionRequestTimeout(1000)
                                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
                                .build()) // 设置默认请求配置
                .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE) // 连接重用策略 是否能keepAlive
                .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)// 长连接配置,即获取长连接生产多长时间
                .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))// 重试次数 默认3次 此处禁用
                .build();

        Registry<ConnectionSocketFactory> socketFactoryRegistry =
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("https", PlainConnectionSocketFactory.getSocketFactory())
                        .register("http", new PlainConnectionSocketFactory()).build();

        final int conExpire = 15;// 长连接闲置过期时间,可配置
        cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, conExpire,
                TimeUnit.SECONDS);
        cm.setDefaultConnectionConfig(ConnectionConfig.DEFAULT);
        cm.setMaxTotal(MAX_CONNECTION_NUM);
        cm.setDefaultMaxPerRoute(MAX_PER_ROUTE);
        // 设置长连接心跳检测,设置超时,禁用nagle算法
        cm.setDefaultSocketConfig(SocketConfig.custom().setSoKeepAlive(true).setSoTimeout(DEFAULT_READ_TIMEOUT).setTcpNoDelay(true).build());
        cm.setValidateAfterInactivity(-1);// 每次取重用的连接时禁止连接活性检测,可以提升性能

        builder.setConnectionManager(cm);
        builder.setConnectionManagerShared(false);

        httpClient = builder.build();

        // 过期检测
        Thread staleCheckThread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(10000);
                    cm.closeExpiredConnections();
                    cm.closeIdleConnections(conExpire, TimeUnit.SECONDS);
                } catch (Exception e) {
                    logger.error("stale check exception", e);
                }
            }

        }, "HttpInvoker-coonection-stale-check-thread");
        // 设置成为守护线程
        staleCheckThread.setDaemon(true);
        staleCheckThread.start();

        // 程序退出时,关闭httpClient
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                httpClient.close();
                logger.info(">>>>httpClient closed<<<<");
            } catch (Exception e) {
                logger.warn("httpClient close exception", e);
            }
        }, "srsynchronize-httpInvoker-shutdown-thread"));
    }

    private void initObjectMapper() {
        objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
    }

    /**
     * 暴露给业务层调用的方法
     * @param path          请求url
     * @param req           请求对象
     * @param typeReference 响应对象
     * @param <R>
     * @return
     * @throws Exception
     */
    public <R extends Response> R execute(String path, Object req, TypeReference<R> typeReference) throws Exception {
        if (typeReference == null) {
            typeReference = (TypeReference<R>) new TypeReference<Response>() {
            };
        }

        String reqBody = objectMapper.writeValueAsString(req);
        logger.info("reqBody:{}", reqBody);

        HttpPost httpPost = new HttpPost(path);
        httpPost.setProtocolVersion(HttpVersion.HTTP_1_1);
        httpPost.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_KEEP_ALIVE);
        httpPost.setConfig(RequestConfig.custom().setSocketTimeout(DEFAULT_READ_TIMEOUT)
                .setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT).build());

        httpPost.addHeader("Accept", DEFAULT_ACCEPTTYPE);
        httpPost.addHeader("Content-Type", DEFAULT_ACCEPTTYPE);

        HttpEntity httpEntity = new StringEntity(reqBody, DEFAULT_CHARSET);
        httpPost.setEntity(httpEntity);

        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        String responseBody = EntityUtils.toString(httpResponse.getEntity(), DEFAULT_CHARSET);
        logger.info("resBody:{}", responseBody);
        return objectMapper.readValue(responseBody, typeReference);
    }

    /**
     * demo
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        HttpTemplate httpTemplate = new HttpTemplate();
        Order order = new   Order();
        order.id = "1111111";
        order.cancleRemarks = "擦擦擦";
        order.createDate = LocalDateTime.now();
        order.startLongitude = "1356.4343";
        OrderResponse orderResponse = httpTemplate.execute("http://127.0.0.1:8888/ordersdispatch", order, new TypeReference<OrderResponse>() {
        });

        //不传具体的OrderResponse会使用默认的Response
        Response response = httpTemplate.execute("http://127.0.0.1:8888/orderdispatch", order, null);

        logger.info("orderResponse:{}", orderResponse);
        logger.info("response:{}", response);
    }

}

public class Order {

	public String id;
	public String orderCode;
	public String startCityName;
	public String startAddress;
	public String startAddDtl;
	public String startLongitude;
	public String startLatitude;
	public String endCityName;
	public String endAddress;
	public String endAddDtl;
	public String endLongitude;
	public String endLatitude;
	public byte prodType;
	public LocalDateTime orderDate;
	public int orderDay;
	public String flightNo;
	public String remark;
	public int orderState;
	public String driverId;
	public String driverUid;
	public String driverMobile;
	public String source;
	public LocalDateTime createDate;
	public LocalDateTime updateDate;
	public String dispatchType;
	public String driverRemark;
	public LocalDateTime flightDate;
	public String flightArrCode;
	public String flightDepCode;
	public String cancleRemarks;
	public LocalDateTime endTime;

}
/**
 * http响应实体
 */
public class Response {
    public static int SUCCESS_CODE = 0;
    public Integer retCode;
    public String retMsg;
    public static boolean isSuccess(Response res) {
        return res != null && res.retCode == SUCCESS_CODE;
    }
    @Override
    public String toString() {
        return "Response{" +
                "retCode=" + retCode +
                ", retMsg='" + retMsg + '\'' +
                '}';
    }
}
public class OrderResponse extends Response {

    public List<Order> info;

}
**欢迎关注公众号**
**微信扫一扫**
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程猿薇茑

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值