使用AsyncHttpClient发送异步请求

背景

我们知道可以用HttpClient来发送同步请求,在并发量大的情况下使用HttpClient的连接池来提高性能。此方法虽然很有效果,但是当访问量极大或网络不好的情况下也会出现某些网络请求慢导致其它请求阻塞的情况。所以我们可以将网络请求变成一个异步的请求,不影响其它的请求。

那么异步请求怎么发送呢?异步请求我们可以使用AsyncHttpClient类来进行实现。

主方法:

/**
 * 执行异步请求
 */
@Component
public class AbcHttpClientService implements InitializingBean {

    private final static String ABC_URL_PREFIX = "http://www.abc.com/";

    private CloseableHttpAsyncClient httpAsyncClient;  //异步httpclient

    private volatile RequestConfig config; //网络环境配置器

    @Autowired
    private ILogService logService;

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }

    /**
     * 发送异步请求
     * @param reqParamMap
     * @return
     */
    public AbcHttpFutureCallback doGet(Map reqParamMap){
        CloseableHttpAsyncClient httpAsyncClient = getHttpClient();

        String reqUrl = HttpUtil.buildUrl(ABC_URL_PREFIX,null,reqParamMap);  //根据参数拼URL

        //创建post方式请求对象,向指定的url发送一次异步post请求
        HttpPost httpPost = new HttpPost(reqUrl); //请求Abc的URL
        httpPost.setConfig(config);

        long begin = System.currentTimeMillis();
        AbcHttpFutureCallback callback = null;

        try{
            CountDownLatch latch = new CountDownLatch(1);
            callback = new AbcHttpFutureCallback(JSON.toJSONString(reqParamMap),reqUrl,begin, this.config.getConnectTimeout(), latch,
                    ThreadLocalHolder.getThreadHolder(), logService);
            //执行请求操作,并拿到结果(异步)
            httpAsyncClient.execute(httpPost,callback);
        }catch(Exception e){

            //记录异常日志
            String param = JSON.toJSONString(reqParamMap);
            String logStr = AdamExceptionUtils.getStackTrace(e);
            if (TimeoutUtil.isTimeOut(e)) {
                logService.sendOverTimeAccountLog(param, logStr, reqUrl, "Abc请求超时");
            } else {
                logService.sendTechnologyErrorAccountLog(param, logStr, reqUrl, "Abc请求异常");
            }
        }

        return callback;
    }

    private void init() throws Exception {
        if (null != this.httpAsyncClient) {
            return;
        }
        refresh();
    }

    public synchronized void refresh() {
        String AbcServiceTimeout = ConfigContainer.getProperty(Constants.ABC_SERVICE_TIMEOUT);
        Integer timeout = 80;
        if (StringHelper.isNumber(AbcServiceTimeout)) {
            timeout = Integer.valueOf(AbcServiceTimeout);
        }
        this.config = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout)
                .setConnectionRequestTimeout(timeout).build();
        try {
            this.httpAsyncClient = AsynHttpClientHelper.initAsynHttpClient(timeout, false);
        } catch (Exception e) {
            this.httpAsyncClient = null;
        }
    }

    /**
     * 获取httpclient
     */
    private CloseableHttpAsyncClient getHttpClient() {
        if (null == this.httpAsyncClient) {
            for (int i = 0; i < 3; i++) {
                try {
                    init();
                } catch (Exception e) {
                    String logStr = AdamExceptionUtils.getStackTrace(e);
                    logService.sendTechnologyErrorAccountLog("", logStr, "", "Abc getHttpClient系统异常");
                }
            }
        }
        return this.httpAsyncClient;
    }
}
public class AsynHttpClientHelper {

    public static CloseableHttpAsyncClient initAsynHttpClient(int timeout, boolean isSetTimeout) throws Exception {
         // 设置协议http对应的处理socket链接工厂的对象
        Registry sessionStrategyRegistry = RegistryBuilder. create().register("http", NoopIOSessionStrategy.INSTANCE).build();

        // 设置连接池大小
        ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor();
                // 初始化连接池
        PoolingNHttpClientConnectionManager connectionManager = new PoolingNHttpClientConnectionManager(ioReactor, sessionStrategyRegistry);
        MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(200).setMaxLineLength(20000).build();
        ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Consts.UTF_8).setMessageConstraints(messageConstraints).build();

        connectionManager.setDefaultConnectionConfig(connectionConfig);

        // 设置连接池的属性
        connectionManager.setMaxTotal(500); // 连接池最大数
        connectionManager.setDefaultMaxPerRoute(500); // 每个路由基础连接数

        CloseableHttpAsyncClient httpAsyncClient = null;
        if (isSetTimeout) {
            //setConnectTimeout:设置连接超时时间,单位毫秒
                        //setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection 超时时间,单位毫秒。
                        //setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。
            RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout).setConnectionRequestTimeout(timeout).build();
            httpAsyncClient = HttpAsyncClients.custom().setConnectionManager(connectionManager).setDefaultRequestConfig(config).build();
        } else {
            httpAsyncClient = HttpAsyncClients.custom().setConnectionManager(connectionManager).build();
        }
        httpAsyncClient.start();
        return httpAsyncClient;
    }

}

回调方法:

//被回调的对象基类,给异步的httpclient使用
public abstract class AbcFutureCallback implements FutureCallback {

    /**
     * 线程专用
     */
    protected ThreadHolder threadHolder = new ThreadHolder();

    public void setThreadHolder(ThreadHolder threadHolder) {
        this.threadHolder.copy(threadHolder);
    }

    public ThreadHolder getThreadHolder() {
        return threadHolder;
    }

}
/**
 * 异步请求结果
 */
public class AbcHttpFutureCallback extends AbcFutureCallback {

    private String url;

    private String param;

    private long starttime;

    private long timeout;

    private String responseString;

    private ILogService logService;

    private CountDownLatch countDownLatch;

    private AbcResponse AbcResponse;   

    public AbcHttpFutureCallback(String param, String url, long starttime, long timeout, CountDownLatch countDownLatch,
                                 ThreadHolder threadHolder, ILogService logService){
        this.param = param;
        this.url = url;
        this.starttime = starttime;
        this.timeout = timeout;
        this.countDownLatch = countDownLatch;
        this.logService = logService;
        setThreadHolder(threadHolder);
    }

    @Override
    public void completed(HttpResponse response) {
        ThreadLocalHolder.setThreadHolder(threadHolder);
        HttpEntity entity = response.getEntity();
        byte[] bytes = null;

        //解析异步请求回调结果
        try{
            Header contentEncodingHeader = response.getFirstHeader("Content-Encoding");
            if (contentEncodingHeader != null && contentEncodingHeader.getValue().toLowerCase().indexOf("gzip") > -1) {
                bytes = EntityUtils.toByteArray(new GzipDecompressingEntity(entity));
            } else {
                bytes = EntityUtils.toByteArray(response.getEntity());
            }
            responseString = new String(bytes, "utf-8"); 
        }catch (Exception e){
            responseString = String.format("Abc entity error: bytes: %s error msg: %s", bytes,
                    AdamExceptionUtils.getStackTrace(e));
        }

        if (null == responseString || isSuccess(response.getStatusLine().getStatusCode()) == false) {
            if (null != logService) {
                logService.sendErrorAccountLog("Abc异步请求结果异常: 无结果");
            }
            responseString = responseString == null?"Abc异步请求返回结果为空" : responseString;
        } else {
            responseString = responseString.replace("'", "'");
            responseString = responseString.replace("&", "&");
            responseString = responseString.replace(" ", " ");

            try{
                AbcResponse = JsonUtil.fromStr(responseString,AbcResponse.class);
            }catch (Exception ex){
                String logStr = AdamExceptionUtils.getStackTrace(ex);
                logService.sendOverTimeAccountLog(param, logStr, url, "Abc异步请求返回结果解析异常");
            }
        }


        //记录日志
        if (logService.isNeedLog()) {
            long end = System.currentTimeMillis();
            RequestLogEntity requestLogEntity = new RequestLogEntity();
            requestLogEntity.setUrl(url);
            requestLogEntity.setHeader("");
            requestLogEntity.setRequest(param);

            requestLogEntity.setResponse(responseString);
            requestLogEntity.setUseTime(end - starttime);
            logService.sendRequestLog(requestLogEntity);
        }

        countDownLatch.countDown();  //利用countDownLatch将异步多线程结果同步返回
    }

    /**
     * 请求失败后调用该函数
     * @param ex
     */
    @Override
    public void failed(Exception ex) {
        ThreadLocalHolder.setThreadHolder(threadHolder);
        String logStr = AdamExceptionUtils.getStackTrace(ex);
        if (TimeoutUtil.isTimeOut(ex)) {
            logService.sendOverTimeAccountLog(param, logStr, url, " Abc 请求超时");
        } else {
            logService.sendTechnologyErrorAccountLog(param, logStr, url, " Abc 方法异常");
        }
    }

    /**
     * 请求取消后调用该函数
     */
    @Override
    public void cancelled() {
        ThreadLocalHolder.setThreadHolder(threadHolder);
        logService.sendTechnologyErrorAccountLog(param, "canceled", url, "  Abc 方法异常");
    }

    public ILogService getLogService() {
        return logService;
    }

    public long getTimeout() {
        return timeout;
    }

    public CountDownLatch getCountDownLatch() {
        return countDownLatch;
    }

    public AbcResponse getAbcResponse() {
        return AbcResponse;
    }

    public void setAbcResponse(AbcResponse AbcResponse) {
        this.AbcResponse = AbcResponse;
    }

    private static boolean isSuccess(int respStatusCode) {
        if (respStatusCode == HttpStatus.SC_OK || respStatusCode == HttpStatus.SC_CREATED) {
            return true;
        } else {
            return false;
        }
    }
}
@Component
public class LogService implements ILogService {
     // 其余方法省略
  
    private boolean isNeedInfoLog() {
        if (null == ThreadLocalHolder.getRequestLogFlag()) {
            return false;
        }
        return ThreadLocalHolder.getRequestLogFlag() >= 2;
    }

    /**
     * 0 表示不需要log,1以上表示需要
     */
    private boolean isNeedErrorLog() {
        if (null == ThreadLocalHolder.getRequestLogFlag()) {
            return false;
        }
        return ThreadLocalHolder.getRequestLogFlag() >= 1;
    }
}
public class ThreadLocalHolder {
    private static ThreadLocal contextHolder = new ThreadLocal();

    public ThreadLocalHolder() {
    }

    public static Integer getRequestLogFlag() {
        if (null == contextHolder.get()) {
            initRunningAccount();
        }

        return ((ThreadHolder)contextHolder.get()).getRequestLogFlag();
    }

    public static void setRequestLogFlag(Integer requestLogFlag) {
        if (null == contextHolder.get()) {
            initRunningAccount();
        }

        ((ThreadHolder)contextHolder.get()).setRequestLogFlag(requestLogFlag);
    }

    public static ThreadHolder getThreadHolder() {
        return (ThreadHolder)contextHolder.get();
    }

    public static void setThreadHolder(ThreadHolder threadHolder) {
        contextHolder.set(threadHolder);
    }

   // 其余方法省略
}

【备注】
之所以要在每一个方法开头都进行ThreadLocalHolder.setThreadHolder(threadHolder);操作的原因是:因为异步调用会另外开启多线程来进行操作,当进行回调的时候需要当前线程的信息来进行日志的记录,所以要在ThreadLocalHolder.setThreadHolder(threadHolder)指明当前线程。其实主要就是为了记录日志。

获取callback的结果:

public class AbcHttpResultGetter{
    protected AbcFutureCallback futureCallback;

    public void setFutureCallback(AdsFutureCallback futureCallback) {
        this.futureCallback = futureCallback;
    }

    @Override
    protected AbcResponse getResultFromCallback() {
        if (null == this.futureCallback || !(this.futureCallback instanceof AbcHttpFutureCallback)) {
            return null;
        }

        AbcHttpFutureCallback callback = (AbcHttpFutureCallback)this.futureCallback;
        CountDownLatch latch = callback.getCountDownLatch();
        ILogService logService = callback.getLogService();

        try{
            //阻塞主线程,直到latch减到0,才执行后面的程序
            if (!latch.await(callback.getTimeout(), TimeUnit.MILLISECONDS)) {
                // 超时的话就调用failed()方法
                callback.failed(new RuntimeException("Abc CountDownLatch wait timeout"));  
            }
            return callback.getAbcResponse();
        }catch (Exception e){
            if (null != logService) {
                logService.sendErrorAccountLog(" Abc CountDownLatch wait error:" + AdamExceptionUtils.getStackTrace(e));
            }
            return null;
        }
    }
}

下面我们新建一个测试类来看看效果:

public class AbcHttpClientServiceTest {

    @Mock
    private ILogService logService;

    @InjectMocks
    private AbcHttpClientService AbcHttpClientService;

    public AbcHttpClientServiceTest() {
        ThreadLocalHolder.setThreadHolder(new ThreadHolder());
        //因为要debug,所以超时时间设置的长一些
        AdsSystemPropertyContainer.addSysConfig(Constants.ABC_SERVICE_TIMEOUT, "400000");  
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void doGetTest() throws InterruptedException {
        Map reqParamMap = new HashMap<>();
        reqParamMap.put("user", "f8b99fd6-e6f7-375f-9532-772a86e55c");
        reqParamMap.put("userid", "207887422");

        AbcHttpFutureCallback callback = AbcHttpClientService.doGet(reqParamMap);

        AbcHttpResultGetter resultGetter = new AbcHttpResultGetter();
        resultGetter.setFutureCallback(callback);
        AbcResponse result = resultGetter.getResultFromCallback();
        System.out.println(rresult.toString());
    }

}

你可能感兴趣的