互联网那些事之数据丢失

 

互联网那些事之数据丢失

本系列故事的全部案例和解决方案只是笔者之前在互联网工做期间的一些事例,仅供你们参考,实际操做应该根据业务和项目状况设计,欢迎你们留言提出宝贵的意见java

背景

小王和小明分别维护分布式系统中A、b两个服务,有一个场景是 A服务会向B服务经过MQ发送事件而且推送用户信息,而后B服务保存用户信息。
在这里插入图片描述spring

有一天,小王和小明由于一件事讨论得热火朝天、各执己见,事情由来以下:数据库

  • 风控部的童鞋找小明说在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计算得出来的一个值,例如:MD五、SHA-2,保证每一个不一样请求的hash码不同,相同的请求hash码相同,能够用于幂等控制。

表大体操做流程:

异常消息状态设计

异常消息有4个状态

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

事件恢复模块

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


异常分类

由于并不是全部的异常都能重跑就能解决问题,咱们只能针对能够修复的异常进行重试,这里把异常分为两大类:

  • 可修复异常:可修复异常指的是能够经过重跑解决的异常,如:数据库超时、数据库缺乏字段、Redis获取锁失败、处理逻辑有问题致使信息缺失、系统升级致使消费失败、网络问题、服务器不稳定等引发。
    • 可当即修复异常:指一些能够经过当即重试就能恢复的异常。例如短暂的网络中断引发的异常,通常能够在功能代码级进行当即重试,可使用spring-retry等组件
    • 延迟修复异常:指一些短期内不能当即恢复的异常,须要延迟执行,等待故障修复。例如依赖的下游系统正在升级,致使一段时间服务接口中断不能够用,须要等待服务启动才能使用,通常经过定时任务设定必定时间间隔或者重跑次数去解决
    • 人工修复异常:指系统没办法直接修复,出现了一些未知异常或者短期内不可解决的异常,例如Redis宕掉没法预知修复时间、上线时脚本遗漏致使表里缺乏字段等,须要人工干预进行重跑,通常经过后台管理页面操做
  • 不可修复异常:不可修复异常指不能经过重跑就能解决的异常。如:上游系统传输格式有问题、消息事件内容自己有误等引发的异常,这些即便重跑也解决不了问题,应该要从上游系统或者根源去解决。

B服务异常处理流程

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

相关文章
相关标签/搜索