互联网那些事 | MQ数据丢失
本系列故事的所有案例和解决方案只是笔者以前在互联网工作期间的一些事例,仅供大家参考,实际操作应该根据业务和项目情况设计,欢迎大家留言提出宝贵的意见
背景
小王和小明分别维护分布式系统中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数据丢失的更多相关文章
- 老杜告诉你java小白到大神是怎么炼成的(转载)
老杜告诉你java小白到大神是怎么炼成的 1. 学习前的准备 一个好的学习方法(应该怎么学习更高效): 一个合格的程序员应该具备两个能力 有一个很好的指法速度(敲代码快) 有一个很好的编程思想(编程思 ...
- 最课程阶段大作业06:U度节能平台控制系统
除了互联网项目,当今社会还有一个概念非常流行,那就是:物联网.什么是物联网?物联网是通过传感设备,按约定的协议,把任意物品与互联网相连接,进行信息交换和通信,以实现智能化识别.定位.跟踪.监控和管理的 ...
- IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事
IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)_互联网的一些事 IT码农哥放弃50万年薪:辞职卖咖喱凉皮(背后深藏功与名)
- Aaron Swartz – 互联网天才开挂的人生历程:每时每刻都问自己,现在这世界有什么最重要的事是我能参与去做的?
Aaron说的一句话让我挺有感触的-- 相信你应该真的每时每刻都问自己,现在这世界有什么最重要的事是我能参与去做的? 如果你没在做那最重要的事,那又是为什么? 1986年11月8日,有个叫Aaron ...
- 大型互联网架构概述 关于架构的架构目标 典型实现 DNS CDN LB WEB APP SOA MQ CACHE STORAGE
大型互联网架构概述 目录 架构目标 典型实现 DNS CDN LB WEB APP SOA MQ CACHE STORAGE 本文旨在简单介绍大型互联网的架构和核心组件实现原理. 理论上讲,从安装配置 ...
- Python/MOOC /翻Wall和互联网编程的那些事
Python MOOC 翻Wall和互联网编程的那些事 声明: 1)本报告由博客园bitpeach撰写,版权所有,免费转载,请注明出处,并请勿作商业用途. 2)若本文档内有侵权文字或图片等内容,请联系 ...
- 互联网 DBA 需要做那些事(转)
众所周知,互联网DBA与传统行业DBA有很大的不同,那就是管理的机器多,新技术更新快,面对的开发多.网络环境复杂.要求7*24待机:这样就 导致互联网DBA的工作在传统DBA工作之上,增加了更多的复杂 ...
- MQ,互联网架构解耦神器
一个架构常识:当调用方需要关心执行结果,通常使用RPC调用. ret = PassportService::userAuth(name, pass); switch(ret){ case(YES) ...
- 聊聊消息队列(MQ)那些事
每年的双十一期间,各大电商平台流量暴增,同时,电商平台系统的负载压力也会很大.譬如订单支付的场景,每个订单支付成功后,服务器可能要完成扣减积分.扣减优惠券.扣减商品库存.发短信等一系列操作.单个用户请 ...
随机推荐
- mysql中information_schema.triggers字段说明
1. 获取所有触发器信息(TRIGGERS) SELECT * FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA='数据库名'; TR ...
- TCP/IP Basic
1.概述 TCP/IP起源于60年代美国政府遮住的一个分组交换网络项目,在当今被定义为互联网通信接口,TCP/IP主要分为4层,每一层负责不同的通信功能,这促成了一个协议族的诞生,而TCP/IP是一组 ...
- EasyMock.replay()有什么用
现在很多项目都使用EasyMock来作为单元测试框架. EasyMock一个方法,基本上是三步:EasyMock.expect().EasyMock.replay().EasyMock.verify( ...
- 理解 RESTful API 设计规范
RESTful是目前最流行的API设计规范,它是用于Web数据接口的设计.从字面可以看出,他是Rest式的接口,所以我们先了解下什么是Rest. REST与技术无关,它代表的是一种软件架构风格,RES ...
- sql函数实用——字符函数(sqlserver与mysql对比)
1.获取长度 sqlserver写法:关键字:len() 获取参数的字符数量 select Len('aksjdhh') 输出结果 7 select len('张无忌ooo') 输出 ...
- MyBatis项目实战 快速将MySQL转换成Oracle语句
一.前言 因项目需求,小编要将项目从mysql迁移到oracle中 ~ 之前已经完成 数据迁移 (https://zhengqing.blog.csdn.net/article/details/103 ...
- FTP服务器虚拟用户配置
FTP服务配置问题及解决方案 使用被动模式,设置云主机IP为被动模式数据传输地址:在配置文件内添加 pasv_enable=YES pasv_promiscuous=YES pasv_address= ...
- Class 'org.apache.tomcat.jdbc.pool.DataSource' not found
把项目移动到新的运行环境时,明明包都导入了,项目也放进tomcat里面了,但是还会找不到该类 解决方法:项目右键选择底下的Properties ->project facets ->jav ...
- 使用gravatar生成头像
avatar代表您在线的图像,当你与网站互动时,你的名字旁边会出现一张图片. Gravatar是一个全球通用的头像.你只需上传一次并创建自己的个人资料,然后当你加入任何支持Gravatar的网站时,你 ...
- Java 几道常见String面试题
String s1="abc"; String s2="abc"; System.out.println(s1==s2); System.out.println ...