本系列故事的所有案例和解决方案只是笔者以前在互联网工作期间的一些事例,仅供大家参考,实际操作应该根据业务和项目情况设计,欢迎大家留言提出宝贵的意见

背景

小王和小明分别维护分布式系统中A、b两个服务,有一个场景是 A服务会向B服务通过MQ发送事件并且推送用户信息,然后B服务保存用户信息。

有一天,小王和小明因为一件事讨论得热火朝天、互不相让,事情由来如下:

  • 风控部的童鞋找小明说在B服务的数据库找不到一些用户资料
  • 小明经过排查,B服务表里确实没有这批用户的数据,在日志里偶尔看到了一些Redis连接超时异常,小明想小王手动帮忙重推试试
  • 小王经过排查,确保自己已经成功推送了那几个用户的数据,并且推送的时候A服务并没有发现MQ异常,觉得自己没有义务去帮忙重推,应该小明自己解决

这时候,在一旁扫地的清洁工老梁过来调解,并帮忙排查分析,导致这个问题的主要原因如下:

  • B服务在接受MQ的处理类捕获了异常,因为异常并没有抛出,所以框架默认自动回复了ACK,MQ认为已经消费者处理成功,就不再重复投放到队列,但此时方法体内因为工具包出现Redis连接超时,抛出异常,导致消息并没有被正常处理

伪代码如下:

    @RabbitHandler
public void handle(byte[] message) {
try {
t = parseBody(messageStr);
} catch (Exception e) {
log.error("消费消息失败", e.getCause());
}
} private void handleMessage(T t) throws MQHandleException {
//唯一标识
String key = t.getLockedId();
//获取锁
DistributedLock lock = DistributedLockFactory.getLock(key);
try {
// 解决分布式服务提交相同资料并发问题
lock.lock(CacheConstants.LOCK_WAIT_TIME, CacheConstants.LOCK_LEASE_TIME, CacheConstants.DEFAULT_CACHE_UNIT);
// 处理业务逻辑
handleBusinessLogic(t);
} catch (LockException e) {
throw new MQHandleException(e);
} finally {
// 释放锁
lock.unLock();
}
}

  • 频繁Redis超时是因为A、B服务共用一个Redis,A服务Key太多把Redis内存资源占满了(也可能连接占满),导致了B服务经常出现连接超时(该故障不是本章主要关注目标)

  • B服务在已经成功接受到消息后,没有把消息先保存起来,所以也导致了自身并没有能力重跑

清洁工老梁跟小王和小明进行一番详谈后,了解到他们主要需求有两个:

  • B服务尽可能自己重新消费信息,而不是一昧依赖A服务手动重推
  • B服务对已接收到的消息,能自己重新消费,当然,这里指的是有意义的消息,如果一些本身A服务推送过来的消息就是有问题的,例如格式错误之类的,这些B服务可以要求A重推

解决思路

经过上面的分析,老梁的解题思路主要分为两个方向:

  • B服务建立自己的本地异常消息事件表。
  • B服务做异常分类,只对可以重跑的消息事件进行重跑

本地异常消息事件表

一般来说,常见的微服务架构实现最终一致性有三种模式:可靠事件模式、业务补偿模式、TCC模式。这里AB服务是通过业务补偿模式实现最终一致性,但这里又跟我们一般的分布式架构的事务问题不同,这里我们只需要保证B服务能最终把正常消息事件消费成功即可。

实现思路:

  • 建立一张本地异常消息事件表,为了避免太多数据库IO操作,这里只会记录异常事件
  • 提取一个通用消息处理层,统一保存异常消息事件,并进行状态更新
  • 提取一个事件恢复模块,统一对失败事件进行追踪
  • 对于重跑仍失败消息事件,设置一个重跑次数上限,进行自动重跑,可以通过调度任务去做(事件恢复模块),当重跑多次仍然失败(像网络异常和数据库异常之类,短时间不会被修复),则后期进行人工重跑

表设计


针对于B服务,对于收到的MQ信息没有进行有效的记录,而且MQ信息处理之后,存在修改错误,没法进行对应信息补充修复的功能,增加通用消息处理层,进行消息体的记录和回溯。 在获取消息之后进行一次记录,进行幂等操作和对应的状态更新, 消息状态在业务相关操作完成后,标记为处理完成,认为对应消息状态结束。

这里hash_value是对请求体进行hash计算得出来的一个值,例如:MD5、SHA-2,保证每个不同请求的hash码不一样,相同的请求hash码相同,可以用于幂等控制。

表大致操作流程:

异常消息状态设计

异常消息有4个状态

  • 待处理 当系统消费失败时,会对特定的异常插入异常事件表,初始状态为 待处理
  • 处理中 当失败恢复模块开始执行任务时会把当前异常事件状态设置为 处理中
  • 处理完成 当失败事件重跑成功后,会把当前异常事件状态设置为 处理完成
  • 异常 当失败事件重跑超过上限次数后,会把当前异常事件状态设置为 异常,等待后期人工重跑

事件恢复模块

失败事件队列在这里是采用数据库表代替


异常分类

因为并非所有的异常都能重跑就能解决问题,我们只能针对可以修复的异常进行重试,这里把异常分为两大类:

  • 可修复异常:可修复异常指的是可以通过重跑解决的异常,如:数据库超时、数据库缺少字段、Redis获取锁失败、处理逻辑有问题导致信息缺失、系统升级导致消费失败、网络问题、服务器不稳定等引起。

    • 可立即修复异常:指一些可以通过立即重试就能恢复的异常。例如短暂的网络中断引起的异常,一般可以在功能代码级进行立即重试,可以使用spring-retry等组件
    • 延迟修复异常:指一些短时间内不能立即恢复的异常,需要延迟执行,等待故障修复。例如依赖的下游系统正在升级,导致一段时间服务接口中断不可以用,需要等待服务启动才能使用,一般通过定时任务设定一定时间间隔或者重跑次数去解决
    • 人工修复异常:指系统没办法直接修复,出现了一些未知异常或者短时间内不可解决的异常,例如Redis宕掉无法预知修复时间、上线时脚本遗漏导致表里缺少字段等,需要人工干预进行重跑,一般通过后台管理页面操作
  • 不可修复异常:不可修复异常指不能通过重跑就能解决的异常。如:上游系统传输格式有问题、消息事件内容本身有误等引起的异常,这些即使重跑也解决不了问题,应该要从上游系统或者根源去解决。

B服务异常处理流程

最后小明负责的B服务按照老梁的思路,重新调整了代码,异常处理流程如下:

互联网那些事 | MQ数据丢失的更多相关文章

  1. 老杜告诉你java小白到大神是怎么炼成的(转载)

    老杜告诉你java小白到大神是怎么炼成的 1. 学习前的准备 一个好的学习方法(应该怎么学习更高效): 一个合格的程序员应该具备两个能力 有一个很好的指法速度(敲代码快) 有一个很好的编程思想(编程思 ...

  2. 最课程阶段大作业06:U度节能平台控制系统

    除了互联网项目,当今社会还有一个概念非常流行,那就是:物联网.什么是物联网?物联网是通过传感设备,按约定的协议,把任意物品与互联网相连接,进行信息交换和通信,以实现智能化识别.定位.跟踪.监控和管理的 ...

  3. IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事

    IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事 IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)

  4. Aaron Swartz – 互联网天才开挂的人生历程:每时每刻都问自己,现在这世界有什么最重要的事是我能参与去做的?

    Aaron说的一句话让我挺有感触的-- 相信你应该真的每时每刻都问自己,现在这世界有什么最重要的事是我能参与去做的? 如果你没在做那最重要的事,那又是为什么? 1986年11月8日,有个叫Aaron ...

  5. 大型互联网架构概述 关于架构的架构目标 典型实现 DNS CDN LB WEB APP SOA MQ CACHE STORAGE

    大型互联网架构概述 目录 架构目标 典型实现 DNS CDN LB WEB APP SOA MQ CACHE STORAGE 本文旨在简单介绍大型互联网的架构和核心组件实现原理. 理论上讲,从安装配置 ...

  6. Python/MOOC /翻Wall和互联网编程的那些事

    Python MOOC 翻Wall和互联网编程的那些事 声明: 1)本报告由博客园bitpeach撰写,版权所有,免费转载,请注明出处,并请勿作商业用途. 2)若本文档内有侵权文字或图片等内容,请联系 ...

  7. 互联网 DBA 需要做那些事(转)

    众所周知,互联网DBA与传统行业DBA有很大的不同,那就是管理的机器多,新技术更新快,面对的开发多.网络环境复杂.要求7*24待机:这样就 导致互联网DBA的工作在传统DBA工作之上,增加了更多的复杂 ...

  8. MQ,互联网架构解耦神器

    一个架构常识:当调用方需要关心执行结果,通常使用RPC调用. ret = PassportService::userAuth(name, pass); switch(ret){  case(YES) ...

  9. 聊聊消息队列(MQ)那些事

    每年的双十一期间,各大电商平台流量暴增,同时,电商平台系统的负载压力也会很大.譬如订单支付的场景,每个订单支付成功后,服务器可能要完成扣减积分.扣减优惠券.扣减商品库存.发短信等一系列操作.单个用户请 ...

随机推荐

  1. X-Admin&ABP框架开发-代码生成器

    在日常开发中,有时会遇到一些相似的代码,甚至是只要CV一次,改几个名称,就可以实现功能了,而且总归起来,都可以由一些公用的页面更改而来,因此,结合我日常开发中使用到的页面,封装一个适合自己的代码生成器 ...

  2. iptables匹配端口范围,映射,网络状态

    ####匹配端口范围:iptables -I INPUT -p tcp -m multiport --dport 21,22,23,24 -j ACCEPT <==次选iptables -I I ...

  3. 从零开始のcocos2dx生活(九)CCBReader

    NodeLoaderLibrary是用来存储节点加载器类型的类,通过registerDefaultNodeLoaders()可以注册所有默认类型的加载器 在CocosBuilder的使用手册中: 1. ...

  4. $loj$10222 佳佳的$Fibonacci$ 矩阵快速幂

    正解:矩阵快速幂 解题报告: 我永远喜欢loj! 一看到这个就应该能想到矩阵快速幂? 然后就考虑转移式,发现好像直接想不好想,,,主要的问题在于这个*$i$,就很不好搞$QAQ$ 其实不难想到,$\s ...

  5. 分支结构,for循环,while循环,跳出循环

    #流程控制 概念:通过规定的语句让程序代码有条件的按照一定的方 式执行 顺序结构 按照书写顺序来执行,是程序中最基本的流程结构 选择结构(分支结构.条件结构) 分支结构 单路分支:if(执行的条件){ ...

  6. spark(1.1) mllib 源码分析(三)-决策树

    本文主要以mllib 1.1版本为基础,分析决策树的基本原理与源码 一.基本原理 二.源码分析 1.决策树构造 指定决策树训练数据集与策略(Strategy)通过train函数就能得到决策树模型Dec ...

  7. Qt中设置窗口图标

    转:https://blog.csdn.net/weiren2006/article/details/7438028 1.通过qtcreator新建一个文件filename.qrc,将图片添加到fil ...

  8. matlab读取excel文件中的数据

    1.读取sheet1中的所有数据 1.1首先我们建立一个sheet表,表名为‘111’ 1.2 默认这些文本以及数字都放在sheet1中,我们将此excel选入当前工作目录(必要步骤), 选入当前工作 ...

  9. PHP实现取得HTTP请求的原文【转】

    本文实例讲述了PHP实现取得HTTP请求的原文的方法,具体步骤如下: 1. 取得请求行:Method.URI.协议 可以从超级变量$_SERVER中获得,三个变量的值如下: $_SERVER['REQ ...

  10. Oracle索引大全

    文档结构如下: 前言: Oracle 官方文档对索引的描述真是弱透了,对索引的说明就是一坨……,support也没有很好的资料,下面还是用的官方上的内容经过自己的整理加上网上的资料. 索引类型: 索引 ...