HA-2 - 重试机制

一、重试机制的意义

随着互联网技术的成熟,各种创新型服务让人应接不暇,在征得市场认同的同时为了应对急剧增长业务和不断降低的硬件成本,单机作战的思维已经越来越被边缘化,SOA,分布式服务,微服务的架构设计已成为当下技术支持能力的主流,出门不聊聊这个都不好意思和别人打招呼,然而正如下图所示,业务系统之间的调用关系会形成一个比较复杂的服务依赖拓扑结构,而结构中的每一个节点都有着至关重要的作用,某个节点出现问题(暂且忽略多机多工的情况这里只是为了表明问题)将会带来不可估量的损失。

在这里插入图片描述
本文不谈因技术人员制造的惊天bug导致的服务不可用,单从服务可用性入手来聊一聊失败重试机制的意义,先举几个生产环境中遇到的例子:

  • 客户端发起请求,nginx映射指定后端服务,发现后端服务连通超时
  • 业务服务之间存在调用依赖关系,A服务在调用B服务,B服务受到请求并返回,但是在返回时网络故障,A并未收到结果
  • 生产端发出消息,消费端处理过程异常导致消费失败
  • 用户消费产生积分,积分系统因为能力有限未能及时处理导致用户无法获取相应积分权益
  • db数据和elasticsearch进行数据同步过程,出现中断导致数据不一致
  • 网络传输文件过程中止
  • … …

以上的问题产生的原因可能多种多样:节点网络不通畅,链接超时,通道堵塞,超时配置,宕机等

总之说了这么多,其实我们也能考虑到,所谓重试机制的前提是我们信任并确认遇到的问题是暂时性问题,在该机制下有很大的几率保证业务的成功,否则没有这个前提重试机制的说法就无法成立,正如DBA删库跑路了,服务器重试链接数据库N次也无济于事。所以这个概念和使用场景一定要区分开。

要保证业务的顺利执行却无法保证百分百的数据一致性,及时性,那如何解决类似的问题,就引出了我们今天的话题:失败重试机制

在业务设计阶段,技术人员有必要对业务内部可能出现的错误性风险进行梳理,对需要重试的业务点位做到心中有数,如:依赖调用过程中甲方无法控制另乙方的可用性,一但乙方出现链接超时的情况,那甲方就需要尝试重试链接,我们相信99.9%的情况下乙方是可用的。

二、重试机制的使用原则
  • 梳理可重试类型和使用场景
  • 确定并信任可重试场景的成功率
  • 重试并不代表一定成功也不代表可以无限制的尝试,这样不但解决不了问题还为其他服务可用性造成麻烦(笔者记得有一次在使用dubbo的链接重试机制没有设置合理的重试次数和链接等待时常,使得服务连接数超高,正常业务无法获取链接,最终产生连锁反应导致系统奔溃)
  • 重试机制只是不得已而保证成功率的备用方案,同样也是存在一定的使用风险,某些场景下要确保业务功能的幂等性,从而避免极端情况下重试机制下重复处理的情况
三、重试机制的设计策略
  • 指数退避算法
    (名字听起来高大上,如果不是写文章我也不知道这样叫) 它的定义其实就是我们平时所讲的阶梯式重试方式,在设定好最大重试次数下,重试次数越多时间跨度越长
    例如:第一次失败1s后重试,第二次失败后2s后重试,第三次失败4s后重试开源项目 Apache RocketMQ 是典型使用此法的案例:

    在RocketMQ 的类:org.apache.rocketmq.common.message.MessageExt 提供了getReconsumeTimes方法,该方法是用于获取当前消息消费的重试次数,这样我们可以有效的控制重试次数:
@Override
   protected MQConsumeResult consumeMessage(String tag, List<String> keys, MessageExt messageExt) {
  QueueMessage<String> message = (QueueMessage)Serialization.byte2object(messageExt.getBody());
  WareDTO wareDTO = JSON.parseObject(message.getBody(),WareDTO.class);
   wareService.addWare(wareDTO);
        // 获取该消息重试次数
        int reconsume = messageExt.getReconsumeTimes();
        //根据消息重试次数判断是否需要继续消费
        //消息已经重试了3次,如果不需要再次消费,则返回成功
        if(reconsume ==3){ 
            // todo 
        }
        MQConsumeResult result = new MQConsumeResult();
        result.setSuccess(true);
        return result;
    }

同样我们可以通过以下形式来控制重试的时间间隔:

messageDelayLevel=1s 2s 4s 8s 16m 32s 1m  2m 
  • 数据落库后继补充处理

    即遇到问题先搁置下来,待到合适时机再做尝试,此方法有一定的适用范围,例如:对数据一致性要求严格但对及时性处理较弱的情况,笔者在与第三方进行商品信息同步的过程,需要同步对方大量的商品数据然后按照自己的业务数据模型进行二次处理并落库,但由于对方提供的每个商品都是全量数据且并未标注哪些属性发生了变更,而我方对商品关键属性(价格,库存,上下架状态)同步的及时性要求又较高,如果按照一般方式不管发生何种属性变更都一股脑的直接落库势必会对服务器和solr的IO吞吐能力要求极高,而这期间也存在着部分同步失败的场景,鉴于此种情况采取的设计方案即:拉取商品数据后先判定是否是价格,库存,上下架状态的变更,如果是立即执行,否则视为非关键性属性变更商品放入缓冲表;如果同步失败放入失败重试缓冲表,此两表中的数据我会拉大同步时间间隔,保证数据最终一致性。
    在这里插入图片描述
  • 分布式处理方式

    分布式的好处之一就是提高服务可达性,这也属于其天然能力,多机多功,nginx是常见的HTTP和反向代理服务器,其不但支持负载均衡,同时对容错也有一定的自身处理能力,在nginx的配置文件中,proxy_next_upstream项定义了什么情况下进行重试,官网文档中给出的说明如下:
Syntax:	proxy_next_upstream error | timeout | >invalid_header | http_500 | http_502 | http_503 | >http_504 | http_403 | http_404 | off ...;
Default:	proxy_next_upstream error timeout;
Context:	http, server, location

默认情况下 当请求服务器发生错误或超时时,会尝试到下一台服务器,还有一个参数影响了重试的次数:proxy_next_upstream_tries,官方文档中给出的说明如下:

Syntax:	proxy_next_upstream_tries number;
Default:	proxy_next_upstream_tries 0;
Context:	http, server, location

一句话:不能在一棵树上吊死,你家不行我换下家

四、重试机制的落地能力
  • 开源工具

    我们所遇到的问题很大部分已经被大厂和领域方向专业的技术人所解决,什么场景下存在什么样的问题使用什么样的重试工具,其实只要用好工具就已经能解决我们生产过程中绝大部分的问题,例如:nginx解决了网络通讯中的重制机制,dubbo提供了服务接口管理及接口调用重试机制,mysql连接池或中间件解决了数据库连接的重试机制,RocketMq等消息中间件内部解决了消息队列生产消费过程中的重试机制,Spring retry 项目封装了大量的重试策略,利用注解对原有的业务代码没有侵入可解决业务场景中的重试策略等等等等。
  • 架构思维和经验

    在技术架构设计上如果没有一定的风险意识,当真正遇到这样的问题时会非常被动及,所以能够未雨绸缪的做好预防措施是合格架构师的标准之一
  • 见机取巧

    遵从一句话,技术是为业务而服务(纯技术性公司不在此列),也就是说遇到业务层面的问题,具体情况具体分析,采用当下可采用的最优方法来实现即可,无需纠结这个是否遵从当下技术主流,同样路都是一条条踩出来的,遇到了坑填了他形成经验归结成技术解决方案你就是主流,不然上文提到的商品同步就没法做了。

与其说重试机制是一种解决暂时 性问题的方式,不如说是一种生活方式:没有失败哪来的成功,遇到一次小小的挫折咋能随意承认不行,男人嘛,都懂!
在这里插入图片描述

我的公众号欢迎订阅

Thinkln  Testing

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值