HttpClient连接池小记 - Go语言中文社区

HttpClient连接池小记


**欢迎关注公众号**
**微信扫一扫**
  • 常用的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;

}
**欢迎关注公众号**
**微信扫一扫**
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/zpcandzhj/article/details/98082895
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-08 11:28:34
  • 阅读 ( 1304 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢