IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> HttpClientUtil -> 正文阅读

[网络协议]HttpClientUtil

最近公司有个项目,没有使用OpenFeign或Dubbo这种微服务远程调用框架,使用的是原生的Apache HttpClient。项目中有个定时任务,需要多次调用其他服务对结果进行汇总,为了保证汇总结果的正确性,就需要确保每次远程调用都是成功的。这个有点类似于数据库中的事务,不知道有没有专业的技术用语,这里姑且叫做远程调用事务。

那么我自己对远程调用事务的理解就是,远程调用第三方服务接口时,由于网络抖动等不稳定性,可能会导致调用异常,比如SocketTimeOut等,这时候要保证结果的正确性,就需要事务机制,要么调用都成功,要么调用都回滚。因为当前的业务中只是多次调用接口获取结果,也就是查,而不是增删改这种操作,因此这里的处理方式就简单了很多,只需要有异常时重试即可,如果某次调用重试后仍然异常,那么就记录此次定时任务失败信息到数据库,后续可以进行定时任务补偿。

因此,本文主要是记录如何自定义HttpClient重试策略。
这里先贴两个参考的文章:

  • https://www.codeleading.com/article/2829663803/
  • https://www.cnblogs.com/kingszelda/p/8886403.html

下面是我的代码:

@ConfigurationProperties(prefix = "http.client")
@Configuration
@Data
@ToString
public class HttpClientConfig {

    /**
     * 连接超时时间 http.client.connection-timeout
     */
    private Integer connectionTimeout = 6000;

    /**
     * 请求超时时间 http.client.socket-timeout
     */
    private Integer socketTimeout = 600000;

    private Integer connectionRequestTimeout = 60000;

    /**
     * 重试次数 http.client.retry-times
     */
    private Integer retryTimes = 5;

    /**
     * 重试间隔(s) http.client.retry-interval
     */
    private Integer retryInterval = 3000;

}
@Component
@ConditionalOnBean(HttpClientConfig.class)
public class HttpClientUtil {

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

    private static PoolingHttpClientConnectionManager connectionManager;
    private static RequestConfig requestConfig;
    private static HttpClientConfig httpClientConfig;
    private static final CustomRetryHandler retryHandler = new CustomRetryHandler();

    @Autowired
    public HttpClientUtil(HttpClientConfig httpClientConfig) {
        HttpClientUtil.httpClientConfig = httpClientConfig;
        // 设置连接池
        connectionManager = new PoolingHttpClientConnectionManager();
        // 设置连接池大小
        connectionManager.setMaxTotal(100);
        connectionManager.setDefaultMaxPerRoute(connectionManager.getMaxTotal());

        RequestConfig.Builder configBuilder = RequestConfig.custom();
        // 设置连接超时
        configBuilder.setConnectTimeout(httpClientConfig.getConnectionTimeout());
        // 设置读取超时时间为10分钟
        configBuilder.setSocketTimeout(httpClientConfig.getSocketTimeout());
        // 设置从连接池获取连接实例的超时
        configBuilder.setConnectionRequestTimeout(httpClientConfig.getConnectionRequestTimeout());
        // 在提交请求之前 测试连接是否可用
        configBuilder.setStaleConnectionCheckEnabled(true);
        requestConfig = configBuilder.build();
    }

    /**
     * 发送 POST 请求(HTTP),JSON形式
     * @param apiUrl
     * @param json
     * @return
     */
    public static String doPost(String apiUrl, Object json) throws Exception {
        CloseableHttpClient httpClient = getClient(true);
        String httpStr = null;
        HttpPost httpPost = new HttpPost(apiUrl);
        CloseableHttpResponse response = null;
        try {
            logger.info("http客户端配置:{}", httpClientConfig.toString());
            httpPost.setConfig(requestConfig);
            StringEntity stringEntity = new StringEntity(json.toString(), "UTF-8");// 解决中文乱码问题
            stringEntity.setContentEncoding("UTF-8");
            stringEntity.setContentType("application/json");
            httpPost.setEntity(stringEntity);
            response = httpClient.execute(httpPost);
            if (response != null){
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                    httpStr = EntityUtils.toString(response.getEntity(),"UTF-8");
                } else {
                    logger.error("doPost Exception,status code = {},request uri = {}", response.getStatusLine().getStatusCode(), httpPost.getURI());
                }
            }
        } catch (IOException e) {
            logger.error(LoggerHelper.message("doPost Exception"), e);
            throw new Exception(e);
        } finally {
            if (response != null) {
                try {
                    EntityUtils.consume(response.getEntity());
                } catch (IOException e) {
                    logger.error(LoggerHelper.message("doPost Exception"), e);
                }
            }
        }
        return httpStr;
    }

    /**
     * 自定义重试策略
     */
    static class CustomRetryHandler implements HttpRequestRetryHandler {
        @Override
        public boolean retryRequest(IOException arg0, int retryTimes, HttpContext arg2) {
            boolean retry = false;
            if (retryTimes > httpClientConfig.getRetryTimes()) {//最多重试3次,如果超过3次,则不再重试,直接抛异常
                retry = false;
            }else if (arg0 instanceof UnknownHostException || arg0 instanceof ConnectTimeoutException
                    || !(arg0 instanceof SSLException) || arg0 instanceof NoHttpResponseException) {
                retry = true;
            }else if (HttpClientContext.adapt(arg2).getRequest() instanceof HttpEntityEnclosingRequest){//如果是幂等请求,则直接重试
                retry = true;
            }

            //设置重试时间间隔
            if (retry) {
                try {
                    logger.info("=== 第 {} 次重试,最多 {} 次,间隔 {} 秒 ===",retryTimes,httpClientConfig.getRetryTimes(),httpClientConfig.getRetryInterval()/1000);
                    Thread.sleep(httpClientConfig.getRetryInterval());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return retry;
        }
    }

    /**
     * @param isPooled 是否使用连接池
     * @return
     */
    public static CloseableHttpClient getClient(boolean isPooled) {
        if (isPooled) {
            return HttpClients.custom().setConnectionManager(connectionManager).setRetryHandler(retryHandler).build();
        }
        return HttpClients.createDefault();
    }
}

可以通过配置参数指定重试次数、重试间隔等。
doPost()方法在有异常时直接抛出,外层业务代码捕获后,根据业务判断后续处理逻辑,比如保存操作记录用于后续补偿。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-03-17 22:35:21  更:2022-03-17 22:35:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:51:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码