银行的电子资金转移系统便是一个典型的分布式数据库。虽然各地的账户记录只保存于本地的数据库中,但外地用户能够十分容易地进行远程查询。这主要是因为,分布式数据库系统向用户提供了一个统一的数据 *** 作窗口,使用户不必寻找某一数据的具体位置,更不必远程登录,就可以直接使用整个数据库系统中所有的信息。从实质上看,分布式数据库就是利用网络和软件技术,使物理空间上分散的数据库的各个部分整合为各个用户的信息空间中统一的数据库。
随着信息高速公路的发展,高性能并行计算机系统和大型数据库/信息库,以及分布式计算机系统和分布式数据库将得到进一步的开发,同时还可能涌现出许多新的计算机系统和数据库技术。
/回答内容很长,能看完的少走一个月弯路,绝不抖机灵/提前预警:本文适合Java新手阅读(老手可在评论区给下建议),希望大家看完能有所收获。废话不多少了,先了解一下Java零基础入门学习路线:第一阶段:JavaSE阶段变量、数据类型、运算符二进制和十进制的转化
注释、单行注释、多行注释、文本注释、注释内容和字节码的关系
标识符、关键字、驼峰原则
变量的本质、内存画图、变量声明和初始化
变量的分类和作用域(局部变量、成员变量、静态变量)
常量和Final
基本数据类型介绍
整型变量和整型常量
浮点类型、float、double
char字符型、转义字符
boolean布尔型、if语句使用要点、布尔类型占用空间问题
运算符介绍
算数运算符(二元、自增、自减)
赋值和赋值运算符
关系运算符详解
逻辑运算符、短路运算符详解
位运算符详解
字符串连接符
条件运算符(三元运算符)
运算符优先级问题
自动类型转换详解
强制类型装换详解
基本数据类型装换常见错误、溢出、L问题
使用Scanner获取键盘输入
控制语句控制语句和实现逻辑对应
if单选结构
if_elseif_else多选结构
switch语句_IDEA更换主题
循环_while
循环_for循环_dowhile
嵌套循环
break和continue语句_标签_控制语句底层原理
写一个年薪计算机_百度查问题的秘诀(重要)
个人所得税计算器软件
方法核心详解_天才思维模型教你高手学习思维模式
方法的重载
递归结构讲解_递归头_递归体
面向对象编程-基础面向过程和面向对象的区别
类和对象的概述
类的属性和方法
创建对象内存分析
构造方法(Construtor)及重载
对象类型的参数传递
this关键字
static关键字详解
局部代码块、构造代码块和静态代码块
package和import详解
JavaDoc生成API文档
面向对象编程-进阶面向对象的三大特性
面向对象之封装(Encapsulation)
访问权限修饰符
面向对象之继承(Inheritance)
Object类
方法重写Override
super关键字详解
重写equals()和toString()
继承中对象创建的内存分析
面向对象之多态(Polymorphism)
向上转型
向下转型
instanceof运算符
编译时和运行时详解
final修饰符
抽象类和抽象方法(abstrct)
接口的定义和实现
JDK8的接口新特性
接口应用:内部类比较器Comparable
内部类详解
Java的内存管理与垃圾回收
异常机制异常的概述
异常的执行过程与分析
try-catch-finally捕捉异常
throw抛出异常
throws声明异常
异常继承体系
运行时异常和编译异常
自定义异常
Java常用类Wrapper包装类自动装箱和自动拆箱
包装类的源码分析
String类的使用与内存原理
String类的源码分析
StringBuffer
StringBuilder
字符串处理类性能分析
Date类
System类
DateFormat类
Calendat类
Math类
BigInteger类和BigDecimal类
Random类
枚举类
File类
常见的面试题讲述与分析
数据结构算法
数据结构的概述
线性表
顺序表
链表
栈和队列
树
二叉树
二叉查找树
二叉平衡树
黑红树
图
冒泡排序
选择排序
递归
折半查找
集合(容器)
集合和数组的联系和区别
集合框架体系
ArrayList的使用和源码分析
集合中使用泛型
LinkedList的使用和源码分析
HashSet的使用和源码分析
哈希表及原理
TreeSet的使用和源码分析
比较器Comparable和Comparator
HashMap的使用和源码分析
TreeMap的使用和源码分析
Iterator于ListIterator
Collections工具类
旧集合类Vector、Hashtable
集合总结和选择依据
泛型接口
泛型类
泛型方法
IO流
IO流的概念
IO流的分类及其原理分析
文件流FlieInputStream、FileOutputStream
缓冲流BufferedInputStream、BufferedOutputStream
数据流ObjectInputStream、ObjectOutputStream
序列化和反序列化
转换流InputStreamReader、OutputStreamWriter
打印流PrintWrite和PrintStream
数组流ByteArrayOutputStream、ByteArrayInputStream
使用IO复制文件夹
多线程
进程和线程
线程的创建与启动
创建线程的三种方式对比
线程的生命周期
线程控制
多线程的安全问题与解决办法
线程的同步:同步代码块
线程的同步:同步方法
线程的同步:Lock锁
线程的死锁问题
线程通信
Condition
线程的完整生命周期
线程池ThreadPoolExecutor
ForkJoin框架
ThreadLocal类
网络编程
计算机网络基础知识
网络通信协议
OSI参考模型
TCP/IP参考模型
数据的封装与拆封原理解析
TCP协议
UDP协议
IP地址和端口号
URL和Socket
使用TCP编程实现登录功能
使用UDP编程实现客服系统
使用TCP编程实现文件上传
集合提升寻训练
手写ArrayList
手写单链表
手写Linkedlist
手写HashMap
手写HashSet
最新并发集合类
多线程提升训练
生产者消费者模式扩展
Lock锁和Condition
ReadWriteLock
BlockingQueue
volatile关键字
多线程题目练习
JDK新特征
面试题详解
设计模式
设计模式入门
面向对象设计七大原则
简单工厂模式
工厂方法模式
单例模式
原型模式
装饰模式
适配器模式
外观模式
第二阶段:数据库MySQL基础
数据库基础知识
MySQL基础知识
MySQL8新特征
安装和卸载MySQL8
使用navicat访问数据库
SQL语言入门
创建数据库表
DML
修改删除数据库表
表的完整性约束
表的外键约束
DML扩展
MySQL 查询语句
基本select查询
where子句
函数
group by
having
SQL99-内连接查询
SQL99-外连接查询
SQL99-自连接查询
SQL92-连接查询
不相关子查询
相关子查询
分页查询
数据库对象
索引
事务及其特征
事务的并发问题
事务的隔离级别
存储过程
导入导出数据
JDBC
JDBC概述
使用JDBC完成添加/更新/删除 *** 作
使用JDBC完成查询 *** 作
JDBC常用接口
使用PreparedStatement
使用事务完成银行转账
提取DBUtil工具类
使用Properties读写属性文件
日志框架log4j
开发员工管理系统
第三阶段:JavaEE阶段
Servlet
web开发概述
B/S和C/S架构简介
>
>
Tomcat安装使用
Tomcat目录结构
Servlet概述
Servlet快速入门
Servlet生命周期
读取配置文件信息
>
>
GET和POST区别
解决中文乱码
请求转发与重定向
绝对路径和相对路径
Cookie
Session
ServletContext
ServletConfig
JSP
JSP技术介绍
JSP的执行过程
scriptlet
表达式
声明
JSP指令元素
JSP动作元素
JSP隐式对象
JSP底层原理
九大内置对象
四个作用域
Servlet和JSP的关系和区别
MVC模式
合并Servlet
JavaScript
JavaScript概述与特点
JS基础语法
函数
数组
Math对象
String对象
Date对象
事件event
浏览器开发者工具
console
DOM和BOM
window
location
navigator
history
认识DOM
DOM获取元素
jQuery
jQuery简介及快速入门
jQuery入口函数
jQuery对象与DOM对象互相转换
基本选择器
属性选择器
位置选择器
表单选择器
内容选择器
jQuery事件
jQuery动画效果
DOM *** 作- *** 作文本
DOM *** 作- *** 作属性
DOM *** 作- *** 作元素
直接 *** 作CSS样式
*** 作CSS类样式
购物车案例
表单验证
正则表达式
EL+JSTL+过滤器+监听器
EL介绍及使用
EL取值原理
EL隐含对象
EL逻辑运算
JSTL介绍-核心标签库
JSTL核心标签库
JSTL-格式标签库
Filter原理
Filter生命周期
Filter链
Filter登录验证
Filter权限控制
Listener概述及分类
Listener监听在线用户
Ajax和JSON
Ajax异步请求和局部刷新的原理
使用原生Ajax验证用户唯一性
jQuery Ajax
JSON的格式和使用
主要JSON解析器
Jackson的使用
Jackson的实现原理
使用jQuery Ajax实现三级联动
使用jQuery Ajax实现自动补全
分页和文件上传/下载
分页的意义
理解分页工具类
实现基本分页
实现带查询的分页
文件上传原理
文件上传API
实现文件上传
文件下载原理
文件下载响应头
实现文件下载
第四阶段:框架阶段
MyBatis
MyBatis概述
MyBatis入门配置
基本的CRUD *** 作
核心配置文件详解
Mapperxml基础详解
模糊查询
分页的实现及插件PageHelper的使用
动态sql+sql片段的使用
一对多、多对一的关系处理
注解的使用
一级缓存和二级缓存说明及使用
generator逆向工程使用
Spring
Spring框架简介
Spring官方压缩包目录介绍
Spring环境搭建
IoC/DI容器详解
Spring创建Bean的三种方式
scope属性讲解
Spring中几种注入方式
静态代理设计模式
动态代理设计模式
AOP详解
AOP中几种通知类型
AOP两种实现方式
自动注入
声明式事务
事务传播行为
事务隔离级别
只读事务
事务回滚
基于注解式配置
常用注解
Spring 整合MyBatis
i18n
Spring整合Junit
SpringMVC
MVC架构模式
手写MVC框架
SpringMVC简介
SpringMVC运行原理
基于配置文件方式搭建环境
基于注解方式搭建环境
SpringMVC的跳转及视图解析器的配置
SpringMVC和Ajax的交互
Spring 参数注入
SpringMVC作用域传值
视图解析器
文件下载
文件上传
Spring拦截器/拦截器栈
登录状态验证
SpringMVC容器和Spring容器介绍
异常处理4种方式
SpringMVC5其他常用注解
Maven
Maven简介
Maven原理
Linux安装及注意事项
Maven项目结构
POM模型
Maven 中项目类型
创建WAR类型的Maven项目
scope属性可取值
SSM项目拆分演示
Maven的常见插件讲解
热部署
BootStrap
BootStrap概述
BootStrap栅格系统
BootStrap常用全局CSS样式
常用组件
常用JavaScript插件
RBAC
RBAC概述
RBAC发展历史
基于RBAC的数据库表设计
URL拦截实现
动态菜单实现
密码学
第五阶段:前后端分离阶段
Spring Boot
Spring Boot简介
Spring Boot实现Spring MVC
配置文件顺序及类型讲解
Spring Boot项目结构
Spring Boot 整合MyBatis
Spring Boot 整合Druid
Spring Boot 整合PageHelper
Spring Boot 整合logback
Spring Boot 整合JSP
Spring Boot 整合Thymeleaf
Spring Boot 开发者工具
Spring Boot 异常显示页面
Spring Boot 整合Junit4
Spring Boot 项目打包部署
Spring Boot 整合Quartz
Spring Boot 中Interceptor使用
Spring Boot Actuator
HikariCP
Logback
Logback简介
Logback依赖说明
Logback 配置文件讲解
Logback 控制台输出
Logback 文件输出
Logback 数据库输出
Spring Security
Spring Security简介
Spring Security架构原理
什么是认证和授权
基础环境搭建
自定义认证流程
UserDetailsService和UserDetails
PasswordEncoder
自定义认证结果
授权-访问路径匹配方式
授权-权限管理
基于注解实现权限管理
Thymeleaf整合Security权限管理
Rememberme 实现
退出实现
CSRF
Linux - CentOS 8
Linux简介
VMWare安装及使用
Linux安装及注意事项
Linux目录结构及路径
Linux常用命令
VMWare常用配置
XShell安装及使用
Xftp安装及使用
JDK解压版配置步骤
Tomcat配置步骤
安装MySQL
WAR包部署
Docker
Docker简介
Docker与VM对比
Docker特点
Docker架构
Docker安装与启动
镜像加速配置
Docker镜像 *** 作常用命令
Docker容器 *** 作常用命令
DockerFile
搭建本地镜像仓库
推送镜像到阿里云及本地仓库
Docker容器生命周期
Docker数据管理
Redis
Redis简介
Redis 单机版安装
Redis 数据类型介绍
Redis 常用命令
Redis 持久化方案
Redis 的主从搭建
Redis的哨兵搭建
Redis 的集群搭建
Spring Boot整合Spring Data Redis
Redis的缓存穿透
Redis的缓存雪崩
Redis的缓存击穿
Vue
vsCode和插件安装
webpack介绍
Vue项目创建
Vue模板语法
Vue条件渲染
Vue列表渲染
Vue事件处理
Vue计算属性
Vue Class与Style
Vue表单处理
Vue组件
Vue组件生命周期
Vue 路由配置
Vue Axios网络请求
Vue跨域处理
Vue Element
Mockjs
Swagger
Swagger2简介
Springfox
Swagger2基本用法
Swagger-UI用法
Swagger2配置
Swagger2常用配置
Git/GitEE
Git的下载和安装
Git和SVN对比
Git创建版本库
Git版本控制
Git远程仓库
Git分支管理
Git标签管理
GitEE建库
GitEE 连接及使用
GitEE 组员及管理员配置
第六阶段:微服务架构
FastDFS
分布式文件系统概述
FastDFS简介
FastDFS架构
Tracker Server
Storage Server
FastDFS安装
安装带有FastDFS模块的Nginx
Fastdfs-java-client的使用
创建Fastdfs-java-client工具类
实现文件上传与下载
KindEditor介绍
通过KindEditor实现文件上传并回显
RabbitMQ
AMQP简介
RabbitMQ简介
安装Erlang
安装RabbitMQ
RabbitMQ原理
Spring Boot 集成RabbitMQ
RabbitMQ的交换器
Spring AMQP的使用
Spring Cloud Netflix Eureka
Eureka简介
Eureka和Zookeeper 对比
搭建Eureka注册中心
Eureka 服务管理平台介绍
搭建高可用集群
集群原理
Eureka优雅停服
Spring Cloud Netflix Ribbon
Ribbon简介
集中式与进程内负载均衡区别
Ribbon常见的负载均衡策略
Ribbon的点对点直连
Spring Cloud OpenFeign
Feign简介
Feign的请求参数处理
Feign的性能优化
配置Feign负载均衡请求超时时间
Spring Cloud Netflix Hystrix
Hystrix简介
服务降级
服务熔断
请求缓存
Feign的雪崩处理
可视化的数据监控Hystrix-dashboard
Spring Cloud Gateway
Spring Cloud Gateway简介
Gateway基于配置文件实现路由功能
Gateway基于配置类实现路由功能
Gateway中内置过滤器的使用
Gateway中自定义GatewayFilter过滤器的使用
Gateway中自定义GlobalFilter过滤器的使用
Gateway中使用过滤器实现鉴权
Gateway结合Hystrix实现熔断功能
Spring Cloud Config
什么是分布式配置中心
创建配置中心服务端
创建配置中心客户端
基于Gitee存储配置文件
基于分布式配置中心实现热刷新
Spring Cloud Bus
什么是消息总线
基于消息总线实现全局热刷新
ELK
ElasticSearch介绍
ElasticSearch单机版安装
ElasticSearch集群版安装
ElasticSearch索引管理
ElasticSearch文档管理
ElasticSearch文档搜索
SpringDataElasticSearch访问ElasticSearch
LogStash介绍
基于LogStash收集系统日志
TX-LCN
分布式事务简介
分布式事务两大理论依据
分布式事务常见解决方案
LCN简介
TX-LCN的3种模式
LCN原理
LCN环境搭建及Demo演示
Nginx
Nginx的简介
什么是正向代理、反向代理
Nginx的安装
Nginx配置虚拟主机
Nginx配置服务的反向代理
Nginx的负载均衡配置
Spring Session
Spring Session介绍
通过Spring Session共享session中的数据
通过Spring Session同步自定义对象
Spring Session的Redis存储结构
设置Session失效时间
Spring Session序列化器
MyBatis Plus
MyBatis Plus简介
Spring整合MyBatis Plus
MyBatis Plus的全局策略配置
MyBatis 的主键生成策略
MyBatis Plus的CRUD *** 作
条件构造器EntityWrapper讲解
MyBatis Plus的分页插件配置
MyBatis Plus的分页查询
MyBatis Plus的其他插件讲解
MyBatis Plus的代码生成器讲解
MyBatis Plus的公共字段自动填充
ShardingSphere
简介
数据库切分方式
基本概念
MySQL主从配置
切片规则
读写分离
实现分库分表
第七阶段:云服务阶段
Kafka
Kafka简介
Kafka架构
分区和日志
Kafka单机安装
Kafka集群配置
自定义分区
自动控制
Spring for Apache Kafka
Zookeeper
Zookeeper简介和安装
Zookeeper 数据模型
Zookeeper 单机版安装
Zookeeper常见命令
ZClient *** 作Zookeeper
Zookeeper 集群版安装
Zookeeper 客户端常用命令
Zookeeper分布式锁
RPC
什么是分布式架构
什么是RFC、RPC
>
RestTemplate
RMI实现RPC
基于Zookeeper实现RPC 远程过程调用
Dubbo
SOA架构介绍
Dubbo简介
Dubbo结构图
Dubbo注册中心
Dubbo 支持的协议
Dubbo 注册中心搭建
Spring Boot 整合 Dubbo
Admin管理界面
Dubbo 搭建高可用集群
Dubbo 负载均衡
Spring Cloud Alibaba Dubbo
Spring Cloud Alibaba Dubbo简介
基于Zookeeper发布服务
基于Zookeeper订阅服务
实现远程服务调用处理
Spring Cloud Alibaba Nacos
Spring Cloud Alibaba Nacos简介
搭建Nacos服务器
基于Nacos发布|订阅服务
实现远程服务调用处理
Nacos Config配置中心
Spring Cloud Alibaba Sentinel
Spring Cloud Alibaba Sentinel简介
搭建Sentinel服务器
Sentinel-实时监控
Sentinel-簇点链路
Sentinel-授权规则
Sentinel-系统规则
@SentinelResource注解
持久化规则
Spring Cloud Alibaba Seata
Spring Cloud Alibaba Seata简介
搭建Seata服务器
Seata支持的事务模式-AT模式
Seata支持的事务模式-TCC模式
Seata支持的事务模式-Saga模式
Seata支持的事务模式-XA模式
SeataAT事务模式应用方式
SeataTCC事务模式应用方式
柔性事务TCC
以常见的下单时使用优惠券的场景为例,涉及三个应用: 订单服务、库存服务、优惠券服务:
Try阶段 (用户下单):
依次同步调用锁定商品库存、锁定优惠券
Confirm阶段( 用户支付完成):
更新订单状态为已支付
Cancel阶段 :
场景一: 锁定商品库存成功,锁定优惠券业务处理失败。 整个业务 *** 作失败,释放前一步锁定的商品库存。
场景二: 库存和优惠券都锁定成功了,但是订单超时未支付自动关闭,或者用户主动取消。
释放对应的库存和优惠券。
TCC使用了加锁粒度较小的 柔性事 务 。 如上面的流程,锁定库存、锁定优惠券、订单落库三个 *** 作,并没有遵循ACID的原则包在一个大的事务中整体进行原子性的提交。 而是变成各自独立应用处理的小事务分开处理。 因此也无法保证在同一时刻各个数据源的数据是对应的( 强一致性 ),某些时刻会出现锁定了库存但是订单还没有落库。 TCC追求的是 最终一致性 ,根据业务最终的成功与否,变更参与者的最终状态和业务状态一致。
看到这,了解分布式中BASE理论的会想起软状态和最终一致性,TCC算是BASE理论的一种体现。
BASE理论
BASE:Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)
基本可用:保证核心功能可用。牺牲边缘功能和部分响应时间。比如电商中,核心业务为下单,物流查询、商品评论可适当降级处理。
软状态:因为延迟等因素导致的各个节点在某时刻不一致的状态。
最终一致:最终保持各个节点的数据一致。如上述场景。
2PC和3PC
然后我们又听到2PC的概念,也是分为两阶段,先预留资源再提交,这不和TCC一样吗。的确,二者的两阶段提交的思想确实是一样的。
2PC和TCC的两阶段补偿的区别
但我们说的2PC指的是基于XA规范的两阶段提交。而XA规范定义的DTP分布式事务模型中TM和RM的交互。
DTP 分布式事务模型中的三个角色: AP(应用程序)、TM(事务管理器)、RM(资源管理器)
由此总结
3PC
但是两阶段提交是完美的么,答案是否定的。
这里我们先不分什么TCC的两阶段提交还是基于XA的2PC了,再去想想刚才的下单场景有什么问题:
假如锁定了库存之后,应用或者说协调者崩溃了,后续的工作都没完成。前期被锁定的库存没有人来释放了,最终一致性出现问题了。
3PC即是在这种背景下产生的
3PC中增加了超时机制,来避免上述资源状态永远无法实现最终一致的问题, 然而超时了到底应该是回滚释放还是提交确认呢?
先看看三个阶段干了什么
阶段1:查询资源是否可用,注意只查询不锁定。所有参与者都可提交再进行下一段,降低预留资源时才发现部分参与者不可提交产生回滚的概率。
阶段2:锁定资源
阶段3:提交确认
参与者收到PreCommit后返回超时,释放预留资源,使整个事务在进入阶段3之前完全回滚。
参与者收到DoCommit后返回超时,仍然提交确认。因为能进到阶段3说明协调者已经完成了阶段2对所有参与者的资源预留锁定。虽然大概率整个事务会成功,但如此毕竟不是完全严谨的,脑裂问题仍然存在,仍然会出现某个参与者提交其他参与者返回失败这样数据不一致的问题。
脑裂问题:协调者就是分布式/集群中的大脑,脑裂即协调者(领导者)的作用失效了。比如崩溃了,或者出现了多个协调者,使得参与者的行为没有被统一调度而出现不一致的情况。
针对上述问题,Paxos算法提供了解决方案,每次处理经过分布式节点中的参与者投票决议是否允许提交,得到超过半数投票者的同意后提交 。细节本文不做赘述了。
我们的服务器从单机发展到拥有多台机器的分布式系统,各个系统之前需要借助于网络进行通信,原有单机中相对可靠的方法调用以及进程间通信方式已经没有办法使用,同时网络环境也是不稳定的,造成了我们多个机器之间的数据同步问题,这就是典型的分布式事务问题。
在分布式事务中事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。分布式事务就是要保证不同节点之间的数据一致性。
1、2PC(二阶段提交)方案 - 强一致性
2、3PC(三阶段提交)方案
3、TCC (Try-Confirm-Cancel)事务 - 最终一致性
4、Saga事务 - 最终一致性
5、本地消息表 - 最终一致性
6、MQ事务 - 最终一致性
消息的生产方,除了维护自己的业务逻辑之外,同时需要维护一个消息表。这个消息表里面记录的就是需要同步到别的服务的信息,当然这个消息表,每个消息都有一个状态值,来标识这个消息有没有被成功处理。
发送放的业务逻辑以及消息表中数据的插入将在一个事务中完成,这样避免了业务处理成功 + 事务消息发送失败,或业务处理失败 + 事务消息发送成功,这个问题。
举个栗子:
我们假定目前有两个服务,订单服务,购物车服务,用户在购物车中对几个商品进行合并下单,之后需要情况购物车中刚刚已经下单的商品信息。
1、消息的生产方也就是订单服务,完成了自己的逻辑(对商品进行下单 *** 作)然后把这个消息通过 mq 发送到需要进行数据同步的其他服务中,也就是我们栗子中的购物车服务。
2、其他服务(购物车服务)会监听这个队列;
1、如果收到这个消息,并且数据同步执行成功了,当然这也是一个本地事务,就通过 mq 回复消息的生产方(订单服务)消息已经处理了,然后生产方就能标识本次事务已经结束。如果是一个业务上的错误,就回复消息的生产方,需要进行数据回滚了。
2、很久没收到这个消息,这种情况是不会发生的,消息的发送方会有一个定时的任务,会定时重试发送消息表中还没有处理的消息;
3、消息的生产方(订单服务)如果收到消息回执;
1、成功的话就修改本次消息已经处理完,也就是本次分布式事务的同步已经完成;
2、如果消息的结果是执行失败,同时在本地回滚本次事务,标识消息已经处理完成;
3、如果消息丢失,也就是回执消息没有收到,这种情况也不太会发生,消息的发送方(订单服务)会有一个定时的任务,定时重试发送消息表中还没有处理的消息,下游的服务需要做幂等,可能会收到多次重复的消息,如果一个回复消息生产方中的某个回执信息丢失了,后面持续收到生产方的 mq 消息,然后再次回复消息的生产方回执信息,这样总能保证发送者能成功收到回执,消息的生产方在接收回执消息的时候也要做到幂等性。
这里有两个很重要的 *** 作:
1、服务器处理消息需要是幂等的,消息的生产方和接收方都需要做到幂等性;
2、发送放需要添加一个定时器来遍历重推未处理的消息,避免消息丢失,造成的事务执行断裂。
该方案的优缺点
优点:
1、在设计层面上实现了消息数据的可靠性,不依赖消息中间件,弱化了对 mq 特性的依赖。
2、简单,易于实现。
缺点:
主要是需要和业务数据绑定到一起,耦合性比较高,使用相同的数据库,会占用业务数据库的一些资源。
下面分析下几种消息队列对事务的支持
RocketMQ 中的事务,它解决的问题是,确保执行本地事务和发消息这两个 *** 作,要么都成功,要么都失败。并且,RocketMQ 增加了一个事务反查的机制,来尽量提高事务执行的成功率和数据一致性。
主要是两个方面,正常的事务提交和事务消息补偿
正常的事务提交
1、发送消息(half消息),这个 half 消息和普通消息的区别,在事务提交 之前,对于消费者来说,这个消息是不可见的。
2、MQ SERVER写入信息,并且返回响应的结果;
3、根据MQ SERVER响应的结果,决定是否执行本地事务,如果MQ SERVER写入信息成功执行本地事务,否则不执行;
如果MQ SERVER没有收到 Commit 或者 Rollback 的消息,这种情况就需要进行补偿流程了
补偿流程
1、MQ SERVER如果没有收到来自消息发送方的 Commit 或者 Rollback 消息,就会向消息发送端也就是我们的服务器发起一次查询,查询当前消息的状态;
2、消息发送方收到对应的查询请求,查询事务的状态,然后把状态重新推送给MQ SERVER,MQ SERVER就能之后后续的流程了。
相比于本地消息表来处理分布式事务,MQ 事务是把原本应该在本地消息表中处理的逻辑放到了 MQ 中来完成。
Kafka 中的事务解决问题,确保在一个事务中发送的多条信息,要么都成功,要么都失败。也就是保证对多个分区写入 *** 作的原子性。
通过配合 Kafka 的幂等机制来实现 Kafka 的 Exactly Once,满足了读取-处理-写入这种模式的应用程序。当然 Kafka 中的事务主要也是来处理这种模式的。
什么是读取-处理-写入模式呢?
栗如:在流计算中,用 Kafka 作为数据源,并且将计算结果保存到 Kafka 这种场景下,数据从 Kafka 的某个主题中消费,在计算集群中计算,再把计算结果保存在 Kafka 的其他主题中。这个过程中,要保证每条消息只被处理一次,这样才能保证最终结果的成功。Kafka 事务的原子性就保证了,读取和写入的原子性,两者要不一起成功,要不就一起失败回滚。
这里来分析下 Kafka 的事务是如何实现的
它的实现原理和 RocketMQ 的事务是差不多的,都是基于两阶段提交来实现的,在实现上可能更麻烦
先来介绍下事务协调者,为了解决分布式事务问题,Kafka 引入了事务协调者这个角色,负责在服务端协调整个事务。这个协调者并不是一个独立的进程,而是 Broker 进程的一部分,协调者和分区一样通过选举来保证自身的可用性。
Kafka 集群中也有一个特殊的用于记录事务日志的主题,里面记录的都是事务的日志。同时会有多个协调者的存在,每个协调者负责管理和使用事务日志中的几个分区。这样能够并行的执行事务,提高性能。
下面看下具体的流程
事务的提交
1、协调者设置事务的状态为PrepareCommit,写入到事务日志中;
2、协调者在每个分区中写入事务结束的标识,然后客户端就能把之前过滤的未提交的事务消息放行给消费端进行消费了;
事务的回滚
1、协调者设置事务的状态为PrepareAbort,写入到事务日志中;
2、协调者在每个分区中写入事务回滚的标识,然后之前未提交的事务消息就能被丢弃了;
这里引用一下消息队列高手课中的
RabbitMQ 中事务解决的问题是确保生产者的消息到达MQ SERVER,这和其他 MQ 事务还是有点差别的,这里也不展开讨论了。
先来分析下一条消息在 MQ 中流转所经历的阶段。
生产阶段 :生产者产生消息,通过网络发送到 Broker 端。
存储阶段 :Broker 拿到消息,需要进行落盘,如果是集群版的 MQ 还需要同步数据到其他节点。
消费阶段 :消费者在 Broker 端拉数据,通过网络传输到达消费者端。
发生网络丢包、网络故障等这些会导致消息的丢失
在生产者发送消息之前,通过channeltxSelect开启一个事务,接着发送消息, 如果消息投递 server 失败,进行事务回滚channeltxRollback,然后重新发送, 如果 server 收到消息,就提交事务channeltxCommit
不过使用事务性能不好,这是同步 *** 作,一条消息发送之后会使发送端阻塞,以等待RabbitMQ Server的回应,之后才能继续发送下一条消息,生产者生产消息的吞吐量和性能都会大大降低。
使用确认机制,生产者将信道设置成 confirm 确认模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ 就会发送一个确认(BasicAck)给生产者(包含消息的唯一 deliveryTag 和 multiple 参数),这就使得生产者知晓消息已经正确到达了目的地了。
multiple 为 true 表示的是批量的消息确认,为 true 的时候,表示小于等于返回的 deliveryTag 的消息 id 都已经确认了,为 false 表示的是消息 id 为返回的 deliveryTag 的消息,已经确认了。
确认机制有三种类型
1、同步确认
2、批量确认
3、异步确认
同步模式的效率很低,因为每一条消息度都需要等待确认好之后,才能处理下一条;
批量确认模式相比同步模式效率是很高,不过有个致命的缺陷,一旦回复确认失败,当前确认批次的消息会全部重新发送,导致消息重复发送;
异步模式就是个很好的选择了,不会有同步模式的阻塞问题,同时效率也很高,是个不错的选择。
Kafaka 中引入了一个 broker。 broker 会对生产者和消费者进行消息的确认,生产者发送消息到 broker,如果没有收到 broker 的确认就可以选择继续发送。
只要 Producer 收到了 Broker 的确认响应,就可以保证消息在生产阶段不会丢失。有些消息队列在长时间没收到发送确认响应后,会自动重试,如果重试再失败,就会以返回值或者异常的方式告知用户。
只要正确处理 Broker 的确认响应,就可以避免消息的丢失。
RocketMQ 提供了3种发送消息方式,分别是:
同步发送:Producer 向 broker 发送消息,阻塞当前线程等待 broker 响应 发送结果。
异步发送:Producer 首先构建一个向 broker 发送消息的任务,把该任务提交给线程池,等执行完该任务时,回调用户自定义的回调函数,执行处理结果。
Oneway发送:Oneway 方式只负责发送请求,不等待应答,Producer 只负责把请求发出去,而不处理响应结果。
在存储阶段正常情况下,只要 Broker 在正常运行,就不会出现丢失消息的问题,但是如果 Broker 出现了故障,比如进程死掉了或者服务器宕机了,还是可能会丢失消息的。
防止在存储阶段消息额丢失,可以做持久化,防止异常情况(重启,关闭,宕机)。。。
RabbitMQ 持久化中有三部分:
消息的持久化,在投递时指定 delivery_mode=2(1是非持久化),消息的持久化,需要配合队列的持久,只设置消息的持久化,重启之后队列消失,继而消息也会丢失。所以如果只设置消息持久化而不设置队列的持久化意义不大。
对于持久化,如果所有的消息都设置持久化,会影响写入的性能,所以可以选择对可靠性要求比较高的消息进行持久化处理。
不过消息持久化并不能百分之百避免消息的丢失
比如数据在落盘的过程中宕机了,消息还没及时同步到内存中,这也是会丢数据的,这种问题可以通过引入镜像队列来解决。
镜像队列的作用:引入镜像队列,可已将队列镜像到集群中的其他 Broker 节点之上,如果集群中的一个节点失效了,队列能够自动切换到镜像中的另一个节点上来保证服务的可用性。(更细节的这里不展开讨论了)
*** 作系统本身有一层缓存,叫做 Page Cache,当往磁盘文件写入的时候,系统会先将数据流写入缓存中。
Kafka 收到消息后也会先存储在也缓存中(Page Cache)中,之后由 *** 作系统根据自己的策略进行刷盘或者通过 fsync 命令强制刷盘。如果系统挂掉,在 PageCache 中的数据就会丢失。也就是对应的 Broker 中的数据就会丢失了。
处理思路
1、控制竞选分区 leader 的 Broker。如果一个 Broker 落后原先的 Leader 太多,那么它一旦成为新的 Leader,必然会造成消息的丢失。
2、控制消息能够被写入到多个副本中才能提交,这样避免上面的问题1。
1、将刷盘方式改成同步刷盘;
2、对于多个节点的 Broker,需要将 Broker 集群配置成:至少将消息发送到 2 个以上的节点,再给客户端回复发送确认响应。这样当某个 Broker 宕机时,其他的 Broker 可以替代宕机的 Broker,也不会发生消息丢失。
消费阶段就很简单了,如果在网络传输中丢失,这个消息之后还会持续的推送给消费者,在消费阶段我们只需要控制在业务逻辑处理完成之后再去进行消费确认就行了。
总结:对于消息的丢失,也可以借助于本地消息表的思路,消息产生的时候进行消息的落盘,长时间未处理的消息,使用定时重推到队列中。
消息在 MQ 中的传递,大致可以归类为下面三种:
1、At most once: 至多一次。消息在传递时,最多会被送达一次。是不安全的,可能会丢数据。
2、At least once: 至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。
3、Exactly once:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。
大部分消息队列满足的都是At least once,也就是可以允许重复的消息出现。
我们消费者需要满足幂等性,通常有下面几种处理方案
1、利用数据库的唯一性
根据业务情况,选定业务中能够判定唯一的值作为数据库的唯一键,新建一个流水表,然后执行业务 *** 作和流水表数据的插入放在同一事务中,如果流水表数据已经存在,那么就执行失败,借此保证幂等性。也可先查询流水表的数据,没有数据然后执行业务,插入流水表数据。不过需要注意,数据库读写延迟的情况。
2、数据库的更新增加前置条件
3、给消息带上唯一ID
每条消息加上唯一ID,利用方法1中通过增加流水表,借助数据库的唯一性来处理重复消息的消费。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)