浅析分布式业务一致性方案

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「java_front」一起交流学习


1 场景分析

现在有一种业务场景:A作为消息发送方,处理业务成功后,投递消息。B作为消息接收方,接收消息,处理业务。在这种业务场景中,我们希望A业务处理成功后,B业务也处理成功。这种抽象场景可以体现在具体业务场景:

  • 下单:用户支付成功,订单中心投递消息,物流中心发货
  • 报名:用户报名成功,活动中心投递消息,下游准备物料
  • 买券:用户支付成功,订单中心投递消息,营销中心发券

A和B作为各自独立系统,如果想要保证业务一致性,并不像单体应用那么简单。我认为从三个维度考虑,而不是单点思考这个问题:

  • 业务发送方
  • 业务消费方
  • 监控

2 业务发送方

业务发送方需要保证一件事情:当本业务处理成功后,一定可以投递成功业务消息。通常有两个方案:本地消息表、事务消息。


2.1 本地消息表

本地消息表思想是在业务表的同一个库中,引入消息表,通过数据库本地事务保证业务表和消息表操作强一致性。使用步骤:

  • 第一步:业务表和消息表强一致更新,消息状态为【待发送】
  • 第二步:发送消息至消息队列,修改为消息状态为【已发送】或【发送失败】
  • 第三步:定时任务查询X时间前【待发送】和【发送失败】消息,重新推送至消息队列

2.2 事务消息

RocketMQ事务消息特性可以满足本文业务场景,事务消息原理如下图:

06 rocketmq事务消息原理.jpg


事务消息使用步骤:
  • 第一步:业务方发送半消息至RocketMQ
  • 第二步:RocketMQ返回半消息发送成功结果
  • 第三步:执行业务代码
  • 第四步:业务执行成功,则提交半消息至RocketMQ,此时消息才算真正提交成功。业务执行失败,回滚半消息
  • 第五步:如果业务执行完成后,由于各种原因(例如网络原因)未返回结果,导致半消息无法确定提交还是回滚
  • 第六步:业务需要提供查询接口,RocketMQ回调这个接口,根据结果决定提交还是回滚半消息

3 业务接收方

业务接收方需要注意以下几个维度:

  • 维度一:接收到消息,如果处理成功需要告知RocketMQ消息处理成功,避免重复消息
  • 维度二:接收到消息,如果处理失败需要告知RocketMQ消息处理失败,等待重试消费
  • 维度三:因为RocketMQ重试机制存在,消息可能会被重复消费,所以必须做业务幂等

3.1 维度一

接收到消息后进行业务处理,如果处理成功则告知RocketMQ成功:

public class MessageListenerImpl implements MessageListener {

    @Override
    public Action consume(Message message, ConsumeContext context) {
        boolean result = doBusiness(message);
        if(result) {
            // 业务处理成功
            return Action.CommitMessage;
        } else {
            return Action.ReconsumeLater;
        }
    }
}

3.2 维度二

3.2.1 代码编写

接收到消息后进行业务处理,如果处理则告诉RocketMQ当前消息消费失败,并希望在稍后重新尝试消费:

public class MessageListenerImpl implements MessageListener {

    @Override
    public Action consume(Message message, ConsumeContext context) {
        boolean result = doBusiness(message);
        if(result) {
            return Action.CommitMessage;
        } else {
            // 业务处理失败
            return Action.ReconsumeLater;
        }
    }
}

消费者有三种方式告知RocketMQ消费失败,均会触发重试机制:

public class MessageListenerImpl implements MessageListener {

    @Override
    public Action consume(Message message, ConsumeContext context) {
        boolean result = doBusiness(message);
        if(result) {
            return Action.CommitMessage;
        } else {
            // 方式一
            return Action.ReconsumeLater;
            // 方式二
            throw new RuntimeException("doBusiness fail");
            // 方式三
            return null;
        }
    }
}

3.2.2 重试机制

(1) 顺序消息

如果消费者在处理过程中失败,RocketMQ 消息队列会自动触发重试机制,每隔1秒尝试重新投递该消息。在此期间由于重试机制的运行,应用可能会遇到消息消费被暂时阻塞的现象。


(2) 无序消息
  • 无序消息类型
    • 普通消息
    • 定时消息
    • 延时消息
    • 事务消息
  • 重试次数
    • 默认16次
    • 自定义重试次数,超过16次后重试间隔均为2小时
  • 重试次数:与上一次时间间隔
    • 第1次:10秒
    • 第2次:30秒
    • 第3次:1分钟
    • 第4次:2分钟
    • 第5次:3分钟
    • 第6次:4分钟
    • 第7次:5分钟
    • 第8次:6分钟
    • 第9次:7分钟
    • 第10次:8分钟
    • 第11次:9分钟
    • 第12次:10分钟
    • 第13次:20分钟
    • 第14次:30分钟
    • 第15次:1小时
    • 第16次:2小时
  • 无序消息重试功能只有在集群消费模式下有效。广播模式下消费失败,失败消息将不会被重试,系统会继续消费下一条新消息

3.3 维度三

在计算机科学和数学中幂等(Idempotent)描述一个操作,无论执行多少次结果均相同。幂等性在分布式系统特别重要,因为这些环境中的操作可能会由于网络延迟、重试逻辑而被多次执行。如果一个操作不幂等,重复执行可能会导致错误结果。常见幂等方案:

  • 幂等表
  • 分布式锁
  • 版本控制
  • 状态机

4 监控

4.1 一个悖论

怎样保证一个工程系统的稳定性?有以下两种做法:

  • 思路1:考虑到所有意外情况,针对每一个意外的异常情况分别处理
  • 思路2:接受无法预料到所有意外情况的现实,把兜底方案做好,保证即使出现极端情况,系统也不会崩溃

我们仔细分析思路1会发现这其实是一个悖论。意外情况就是意料之外的情况,无法预料的情况。如果被考虑到了,那么也就不能称之为意外情况了。

塔勒布在经典著作《反脆弱》一直想告诉我们:黑天鹅事件是无法预测的,极端意外情况是无法预测的,尾部风险虽然概率小但破坏力却极大。所以我们要保护好系统。


4.2 事前、事中、事后

如何思考保护系统这个问题?我们可以从三个维度思考:

  • 事前:监控异常,及时响应
  • 事中:快速止血,迅速恢复
  • 事后:数据恢复,定损复盘

4.3 事前监控

异常监控存在三个维度:

  • 系统异常:出现一次就需要感知
  • 业务监控:X分钟出现Y次需要感知
  • 数据监控:数据量不匹配,状态X时间内未流转

5 延伸阅读

反脆弱与技术系统高可用性

分布式事务理论与实例分析


欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「java_front」一起交流学习

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/541254.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Project Euler_Problem 178_Step Numbers_动态规划

原题目&#xff1a; 解题思路&#xff1a;动态规划 代码&#xff1a; ll R[50][11][2048];void solve() {ll i, j,k,x,y,z,p,q,u,v;N 40, NN 1024;//N 20;double a, b, c,d;for (i 0; i < 9; i) {R[1][i][1 << i] 1;}for (i 2; i < N; i) {for (j 0; j &…

三小时零基础入门微信扫码点餐小程序 手把手带你开发一款云开发版点餐软件,店铺地图导航,外卖小程序,用户端和后厨端都有

从今天开始带领大家实现一款云开发版的点餐小程序 视频讲解&#xff1a;《云开发后台微信扫码点餐小程序cms网页管理后台》 技术选型 1&#xff0c;前端 微信小程序原生框架cssJavaScript 2&#xff0c;管理后台 云开发Cms内容管理系统web网页 3&#xff0c;数据后台 小…

推荐几款常用Web自动化测试神器!

1、介绍 Web自动化测试在保证质量、提升效率、软件开发加速迭代上起到关键作用&#xff0c;它已经成为现代软件测试中不可或缺的一部分&#xff0c;今天给大家介绍推荐几款常用的Web自动化测试工具。 2、常用测试工具 常用的Web自动化测试工具包括&#xff1a; Selenium&am…

Vue.js npm错误:transpileDependencies.map不是一个函数

这个错误通常是由于npm版本不兼容导致的。在旧版本的npm中&#xff0c;transpileDependencies是一个字符串数组&#xff0c;我们可以直接配置需要编译的依赖库。而在较新版本的npm中&#xff0c;transpileDependencies被改成了一个对象&#xff0c;并且需要使用map()方法来处理…

有限差分法求解一维、二维波动方程

差分格式方法是数值计算方法中微分以及偏微分导数的一种离散化方法。具体来说&#xff0c;它使用相邻两个或者多个数值点的差分来取代偏微分方程中的导数或偏导数。选择差分格式是离散化偏微分方程的第一步&#xff0c;通过这种离散化&#xff0c;我们可以将连续空间区域上的问…

【UE 委托】如何利用函数指针理解委托的基本原理

目录 0 引言1 函数指针模拟多播委托 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;UE虚幻引擎专栏&#x1f4a5; 标题&#xff1a;【UE 委托】如何利用函数指针理解委托的基本原理❣️ 寄语&#xff1a;书到用时方恨少&#xff0c;事非经过不知难…

适用于 Windows 的 10 个免费数据恢复工具集合

有时&#xff0c;我们都会在个人计算机上意外删除一些重要文件或数据。我们无需再担心此类问题&#xff0c;因为我们可以借助互联网上提供的免费数据恢复工具来恢复宝贵的数据和图像。 互联网上有许多免费的数据恢复工具&#xff0c;从一长串工具中&#xff0c;我们列出了最好…

阿里云优惠口令2024最新

2024年阿里云域名优惠口令&#xff0c;com域名续费优惠口令“com批量注册更享优惠”&#xff0c;cn域名续费优惠口令“cn注册多个价格更优”&#xff0c;cn域名注册优惠口令“互联网上的中国标识”&#xff0c;阿里云优惠口令是域名专属的优惠码&#xff0c;可用于域名注册、续…

【软考中级】软件设计师考点分布

文章目录 软考官网资格设置软考报考流程 【软件设计师】考点分布选择题考点分布案例题考点分布 软考官网 中国计算机技术职业资格网&#xff1a;https://www.ruankao.org.cn/ 官网报名平台&#xff1a;https://bm.ruankao.org.cn/sign/welcome 资格设置 计算机软件计算机网…

RNN知识体系构筑:详尽阐述其理论基础、技术架构及其在处理序列数据挑战中的创新应用

一、为什么需要RNN 尽管神经网络被视为一种强大且理论上能够近似任何连续函数的模型&#xff0c;尤其当训练数据充足时&#xff0c;它们能够在输入空间中的某个点( x )映射到输出空间的特定值( y )&#xff0c;然而&#xff0c;这并不能完全解释为何在众多应用场景中&#xff…

数据结构排序篇上

排序的概念及其运用 排序的概念 排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性 &#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&…

震惊!借助Coze白嫖GPT4-128k解决方案

震惊!某大佬借助Coze白嫖GPT4-128k解决方案 前言 此文章介绍如何免费使用GPT-4高级模型并拓展API功能 最近的 Coze 在国内开放了&#xff0c;可以免费使用大模型。但是和国外的有点区别&#xff0c;国外版本使用的chatgpt4&#xff0c;国内版本使用的是语雀大模型。 Coze是一…

功能测试_订购单检查_判定表

画判定表的步骤&#xff1a; 列出条件 列出动作

964: 数细胞

样例&#xff1a; 解法&#xff1a; 1.遍历矩阵 2.判断矩阵[i][j]&#xff0c;若是未标记细胞则遍历相邻所有未标记细胞并标记&#xff0c;且计数 实现&#xff1a;遍历相邻所有未标记细胞 以DFS实现&#xff1a; function dfs(当前状态) {if (终止条件) {}vis[标记当前状…

设计模式——外观(门面)模式10

外观模式&#xff1a;能为系统框架或其他复杂业务流程封装提供一个简单的接口。 例如抽奖过程中 设计模式&#xff0c;一定要敲代码理解 调用1&#xff08;抽奖系统&#xff09; /*** author ggbond* date 2024年04月08日 10:34*/ public class Lottery {public String getId…

x86处理器工作原理

对于电脑&#xff0c;大家可能司空见惯。但有没有想过它的处理器是如何工作的呢&#xff1f;下面和大家一起学习它的工作原理。 一. 最早的处理器 1.1 技术的发展 1947年&#xff0c;美国贝尔实验室的肖克利和同事们一起发明了晶体管。 1958年&#xff0c;美国人杰克基尔比发…

SpringBoot 集成H2数据库,启动执行sql, 中文乱码

目录 H2数据库介绍 SpringBoot版本&#xff1a;SpringBoot 2.1.12.RELEASE 快速集成H2&#xff0c;maven依赖 快速集成H2&#xff0c;数据源及关键参数配置 spring.datasource.schema参数&#xff08;建表SQL脚本&#xff09; spring.datasource.data参数&#xff08;更新、…

napi系列学习高阶篇——通过IDE集成C/C++三方库并开发napi接口

简介 应用在调用系统固件集成的C/C三方库时&#xff0c;可能会由于系统固件集成端与IDE的NDK中libc版本不一致导致调用失败&#xff0c;而且系统固件集成的C/C三方库对于应用的调式也很不友好&#xff0c;需要多方编译调试&#xff0c;很不方便。因此本文将通过在IDE上适配ope…

16、普通数组-除自身以外的数组乘积

思路 通过辅助数组的方式 第一个从左向右的辅助数组乘积第二次从右向左的辅助数组乘积对于0<i<N-1 他的数组乘积就是左边的数组乘积*右边数组乘积然后再分类讨论i0 就是右边1-N-1的数组乘积iN-1就是左边从N-2到0的数组乘积 代码如下&#xff1a; class Solution {pub…

【MATLAB】GA_ELM神经网络时序预测算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 1 基本定义 GA_ELM&#xff08;Genetic Algorithm and Extreme Learning Machine&#xff09;是一种结合了遗传算法和极限学习机的神经网络时序预测算法。它的核心思想是通过使用遗传算法来优化极限学习机的权重和偏差&…