找回密码
 会员注册
查看: 192|回复: 0

转转门店基于MQ的Http重试实践

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64021
发表于 2024-9-20 08:12:05 | 显示全部楼层 |阅读模式
1问题背景2重试方案探索2.1简单重试2.2ApacheHttpClient重试机制2.3基于消息队列的异步重试方案3门店业务场景中使用的重试方案1问题背景在线下门店系统开发中,有很多地方需要使用Http请求和第三方系统进行通信,比如将门店的商品信息同步到第三方的电子价签上,再比如需要把门店店员的打卡信息同步到公司使用的第三方EHR系统中。但在使用Http请求外部服务时,由于网络的不稳定性,第三方接口出现超时的现象时有发生,为了减少对业务造成的影响,我们迫切需要寻找一种Http重试方案。2重试方案探索2.1简单重试我们最容易想到的一种重试方式是,在请求接口的代码块中加入循环,如果请求失败则继续请求,直到请求成功或达到最大重试次数。示例代码如下:  int retryTimes = 3;  for (int i = 0; i  this.retryCount) {            // Do not retry if over max retry count            return false;        }        if (this.nonRetriableClasses.contains(exception.getClass())) {            return false;        }        final HttpClientContext clientContext = HttpClientContext.adapt(context);        final HttpRequest request = clientContext.getRequest();        if (handleAsIdempotent(request)) {            // Retry if the request is considered idempotent            return true;        }        if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) {            // Retry if the request has not been sent fully or if it's OK to retry methods that have been sent            return true;        }        return false;    }retryRequest的逻辑也比较简单,首先超过重试次数就不会再重试,然后如果是指定不重试的异常也不会再重试;再然后如果请求方法不是幂等的,也不会继续重试,这里我们熟悉的Post方法显然是不会进行重试的。不过还有机会,这里我们知道requestSentRetryEnabled默认是false,也就是说只要请求发送成功之后也不会进行重试。到这里,我们可以总结一下了。HttpClient默认的RetryHandler中指定了四类异常是不会进行重试的,其中就包含了InterruptedIOException,而实际上我们经常会遇到的SocketTimeoutException就属于它的子类。还有一点,如果按照默认的重试策略,显然Post请求也不满足重试的条件。这里必须说一下,从谨慎的角度来看,Post请求是否应该重试,需要具体结合业务场景来看,如果请求本身不是幂等的,重试确实可能会带来严重的副作用。所以在实际的业务场景中,如果想要利用HttpClient的重试机制来进行重试,这两个问题都需要解决。2.3基于消息队列的异步重试方案考虑到在门店很多业务场景中,执行完相关的逻辑之后都会发送MQ消息。那么我们很自然地也想到了通过引入一个消费者的方式,来执行通过Http调用第三方接口的逻辑。采用这种方式的话,如果在消费逻辑中通过Http调用第三方接口失败,我们还可以充分利用MQ的消费失败重试机制。以我们使用的RocketMQ为例,消息在消费失败重试的时候会按照一定的退避时间来进行重试,这个特性还能避免第三方服务因为短时间的不可用而造成的重试失败的情况。3门店业务场景中使用的重试方案经过以上多种方案的调研,我们最终采用的是方案二和方案三的综合方案,具体思路如下。首先,我们整体的重试方案采用基于消息队列的异步执行方案,一方面是因为这种方案可以充分地做到和业务之间解耦,同时消息队列的消费失败重试机制可以很好地解决第三方服务短时间不可用的问题,这一点是同步重试方案做不到的,可以保障系统的最终一致性。其次,因为我们系统中已经在使用HttpClient组件,所以我们决定充分利用它的重试机制,同步重试也可以尽可能保证接口调用的实时性。考虑到默认的重试策略不满足我们的使用需求,针对这个问题,我们自定义了一个RetryHandler,源码如下图:    public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {        if (executionCount > this.retryCount) {            RequestLine requestLine = null;            if (context instanceof HttpClientContext) {                requestLine = ((HttpClientContext)context).getRequest().getRequestLine();            }                        return false;        } else if (exception instanceof NoHttpResponseException) {            return true;        } else if (exception instanceof SSLHandshakeException) {            return false;        } else if (exception instanceof InterruptedIOException) {            return true;        } else if (exception instanceof UnknownHostException) {            return false;        } else if (exception instanceof ConnectTimeoutException) {            return false;        } else if (exception instanceof SSLException) {            return false;        } else {            HttpClientContext clientContext = HttpClientContext.adapt(context);            HttpRequest request = clientContext.getRequest();            return !(request instanceof HttpEntityEnclosingRequest);        }    }完成RetryHandler的自定义之后,只需要在初始化HttpClient的时候传入指定的RetryHandler即可,设置方式如下:  CloseableHttpClient httpClient = HttpClientBuilder.create().setRetryHandler(StoreRequestRetryHandler.INSTANCE).build();这样我们就解决了默认的重试机制对于Post请求默认不重试和SocketTimeoutException异常不重试的问题,更加贴合我们的使用场景。这里我举个例子来说明一下整个重试方案的执行流程:MQ在消费的时候,会使用ApacheHttpClient请求第三方接口,我们设置重试3次,如果请求一直失败,会先同步重试3次,如果还是失败,则本次消息消费失败,等待下一次重试消息继续这个流程。RocketMQ默认会重试16次,那么我们整个重试方案会最多进行51次重试。ApacheHttpClient的同步重试能尽可能保证同步的实时性,而如果第三方服务出现短时间不可用的现象,RocketMQ的退避重试也能继续异步重试只到最终成功。在我们使用了这种重试方案之后,就再也没有听到业务关于电子价签未及时同步或者打卡信息未同步的抱怨了。以上就是笔者在线下门店系统中的Http重试实践过程,欢迎大家在评论区留言一起交流。关于作者侯万兴,转转门店业务后端研发工程师想了解更多转转公司的业务实践,欢迎点击关注下方公众号:
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 01:21 , Processed in 0.678723 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表