技术

如何使用RedisTemplate访问Redis数据结构 MySQL重要知识点 OAuth2认证授授权流程 分布式锁 服务调用 MQ的介绍 SpringCloud 使用链 Eureka 的点对点通信 介绍Eureka RabbitMQ与其它MQ的对比 Springboot 启动过程分析 Springboot 入门 Linux内存管理 自定义CNI IPAM 扩展Kubernetes 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 Kubernetes安全机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占cpu怎么办? 容器狂打日志怎么办? Kubernetes资源调度-scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects之编排对象 源码分析体会 自动化mock AIOps说的啥 从DevOps中挖掘docker的价值 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes整体结构 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 kubernetes实践 线程排队 jib源码分析之细节 从一个签名框架看待机制和策略 跨主机容器通信 jib源码分析及应用 docker环境下的持续构建 docker环境下的持续发布 一个容器多个进程 kubernetes yaml配置 marathon-client 源码分析 《持续交付36讲》笔记 程序猿应该知道的 mybatis学习 无锁数据结构和算法 《Container-Networking-Docker-Kubernetes》笔记 活用linux 命令 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 swagger PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 《深入剖析kubernetes》笔记 精简代码的利器——lombok 学习 java 语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 JVM4——《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 commons-pipeline 源码分析 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS3——论文学习 Unsafe Spark Stream 学习 linux 文件系统 mysql 批量操作优化 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 forkjoin 泛谈 hbase 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 calico 问题排查 bgp初识 mesos 的一些tips mesos 集成 calico calico AQS2——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 compensable-transaction 源码分析 硬件对软件设计的影响 elasticsearch 初步认识 mockito简介及源码分析 线上用docker要解决的问题 《Apache Kafka源码分析》——Producer与Consumer 停止容器 dns隐藏的一个坑 《mysql技术内幕》笔记2 《mysql技术内幕》笔记1 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 java系并发模型的发展 从一个marathon的问题开始的 docker 环境(主要运行java项目)常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 netty中的future和promise 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 一些tricky的code http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 JVM3——java内存模型 java concurrent 工具类 java exception java io涉及到的一些linux知识 network channel network byte buffer 测试环境docker化实践 通用transport层框架pigeon netty(七)netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 从Go并发编程模型想到的 mesos深入 Macvlan Linux网络源代码学习2 《docker源码分析》小结 对web系统的一些理解 docker中涉及到的一些linux知识 hystrix学习 Linux网络源代码学习 Docker网络五,docker网络的回顾 zookeeper三重奏 数据库的一些知识 Spark 泛谈 commons-chain netty(六)netty回顾 Thrift基本原理与实践(三) Thrift基本原理与实践(二) Thrift基本原理与实践(一) Future 回调 Docker0.1.0源码分析 基于spring boot和Docker搭建微服务 通过Docker Plugin来扩展Docker Engine java gc Docker网络四,基于Centos搭建Docker跨主机网络 google guava的一些理解 Jedis源码分析 Redis概述 Docker回顾 深度学习是个什么鬼 Docker网络三,基于OVS实现Docker跨主机网络 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 netty(四)netty对http协议的实现(废弃) netty(三)netty框架泛谈 向Hadoop学习NIO的使用 以新的角度看数据结构 AQS1——并发相关的硬件与内核支持 使用Ubuntu要做的一些环境准备 Docker网络二,libnetwork systemd 简介 那些有用的sql语句 异构数据库表在线同步 spring aop 实现原理简述——背景知识 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 我们编程的那些潜意识 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 一次代码调试的过程 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 docker volume 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM2——JVM和传统OS对比 git spring rmi和thrift maven/ant/gradle使用 再看tcp mesos简介 缓存系统——具体组件 缓存系统 java nio的多线程扩展 多线程设计模式/《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go 常用的一些库 Netty(一)初步了解 java mina Golang开发环境搭建(Windows下) java nio入门 ibatis自动生成类和文件 Python初学 Goroutine 调度模型猜想 一些编程相关的名词 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Hadoop安装与调试 Kubernetes持久化存储 Kubernetes 其它特性 访问Kubernetes上的服务 Kubernetes副本管理 Kubernetes pod 组件 使用etcd + confd + nginx做动态负载均衡 nginx安装与简单使用 在CoreOS集群上搭建Kubernetes 如何通过fleet unit files 来构建灵活的服务 CoreOS 安装 定制自己的boot2docker.iso CoreOS 使用 Go初学 JVM1——jvm小结 硬币和扑克牌问题 LRU实现 virtualbox 使用 os->c->java 多线程 容器类概述 zabbix 使用 zabbix 安装 Linux中的一些点 关于集群监控 ThreadLocal小结 我对Hadoop的认识 haproxy安装 docker快速入门

标签


事务一致性

2017年07月18日

简介

事务一致性属于 一致性问题的一种(另一种是副本一致性),建议先看下 串一串一致性协议

在实际的业务中,分布式事务有以下表现形式:

  1. 跨数据库操作
  2. 数据库与rpc 等混合操作
  3. 多rpc 操作

文章整体结构来自 实践丨分布式事务解决方案汇总:2PC、消息中间件、TCC、状态机+重试+幂等

一个问题不同层面解决

2018.9.26 补充:《左耳听风》中提到:

  1. 对于应用层上的分布式事务一致性

    • 吞吐量大的最终一致性方案:消息队列补偿等
    • 吞吐量小的强一致性方案:两阶段提交
  2. 数据存储层解决这个问题的方式 是通过一些像paxos、raft或是nwr这样的算法和模型来解决。 PS:这可能是因为存储层 主要是副本一致性问题
  3. 在实践中,还是尽量在数据存储层解决分布式事务问题。比如TiDB、OceanBase等,类似于一个无限容量的数据库 ==> 无需跨库操作 ==> 减少分布式事务问题。由此可见,一致性问题可以 “转嫁”

2PC/3PC

一致性协议

JTA与TCC

过程

正常情况

异常情况

无论事务提交,还是事务回滚,都是两个阶段

2pc有很多问题,比如单点、同步阻塞等,此处我只讨论数据一致性问题:在二阶段提交协议的阶段二,即执行事务提交的时候,当协调者向所有的参与者发送Commit请求之后,发生了局部网络异常或者是协调者在尚未发送完Commit请求之前自身发生了崩溃,导致最终只有部分参与者收到了Commit请求。于是,这部分收到了Commit请求的参与者就会进行事务的提交,而其他没有收到Commit请求的参与者则无法进行事物提交,于是整个分布式系统便出现了数据不一致的现象。

正常情况

异常情况

在分布式环境下,分布式系统的每一次请求和响应,存在特有的三态概念:即成功、失败、超时。相对于2pc,3pc处理了timeout问题。但3pc在数据一致性上也有问题:在参与者接收到preCommit消息后,如果出现网络分区,此时协调者所在的节点和参与者无法进行正常的网络通信,在这种情况下,参与者依然会进行事物的提交,就可能出现不同节点成功or失败,这必然出现数据的不一致。

2PC除本身的算法局限外,还有一个使用上的限制,就是它主要用在两个数据库之间(数据库实现了XA协议)。但以支付宝的转账为例,是两个系统之间的转账,而不是底层两个数据库之间直接交互,所以没有办法使用2PC。

在代码上的表现

  1. 声明式事务
  2. 编程式事务

本地事务处理

Connection conn = null; 
try{
    //若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
    conn.setAutoCommit(false);
    // 将 A 账户中的金额减少 500 
    // 将 B 账户中的金额增加 500 
    conn.commit();
}catch(){
     conn.rollback();
}

跨数据库事务处理

UserTransaction userTx = null; 
Connection connA = null; 
Connection connB = null; 
try{
    userTx.begin();
    // 将 A 账户中的金额减少 500 
    // 将 B 账户中的金额增加 500 
    userTx.commit();
}catch(){
     userTx.rollback();
}

代码的封装跟2pc的实际执行过程有所不同,可以参见JTA与TCC

TCC

TCC借鉴2PC的思路,对比上一小节可以看到,调用方充当了协调者的角色。那么它是如何解决2PC的问题的呢?也就是说,在阶段2,调用方发生宕机,或者某个服务超时了,如何处理呢?答案是:不断重试(Confirm/Cancel)!不管是Confirm失败了,还是Cancel失败了,都不断重试。这就要求Confirm和Cancel都必须是幂等操作。注意,这里的重试是由TCC的框架来执行的,而不是让业务方自己去做。

github 案例项目

  1. QNJR-GROUP/EasyTransaction
  2. moonufo/galaxyLight

最终一致性+消息中间件

实践丨分布式事务解决方案汇总:2PC、消息中间件、TCC、状态机+重试+幂等一般的思路是通过消息中间件来实现“最终一致性”

错误的方案:网络调用(rpc/db/mq)和更新DB放在同一个事务里面

这个方案有两个问题

  1. 发送消息失败,发送方并不知道是消息中间件没有收到消息,还是消息已经收到了,只是返回response的时候失败了?
  2. 把网络调用放在数据库事务里面,可能会因为网络的延时导致数据库长事务。严重的会阻塞整个数据库,风险很大。

最终一致性:第1种实现方式(业务方自己实现)

  1. 系统A不再直接给消息中间件发送消息,而是把消息写入到消息表中。把DB1的扣钱操作(表1)和写入消息表(表2)这两个操作放在一个数据库事务里,保证两者的原子性。
  2. 系统A准备一个后台程序,源源不断地把消息表中的消息传送给消息中间件。如果失败了,也不断尝试重传。因为网络的2将军问题,系统A发送给消息中间件的消息网络超时了,消息中间件可能已经收到了消息,也可能没有收到。系统A会再次发送该消息,直到消息中间件返回成功。所以,系统A允许消息重复,但消息不会丢失,顺序也不会打乱。
  3. 系统A保证了消息不丢失

系统B对消息的消费要解决下面两个问题:

  1. 丢失消费,系统B从消息中间件取出消息(此时还在内存里面),如果处理了一半,系统B宕机并再次重启,此时这条消息未处理成功,怎么办?答案是通过消息中间件的ACK机制,凡是系统B(向mq)发送ACK的消息,系统B重启之后消息中间件不会再次推送;
  2. 重复消费。除了ACK机制,可能会引起重复消费;系统A的后台任务也可能给消息中间件重复发送消息。为此,系统B增加一个判重表(业务允许的话也可以直接业务判重)

如果消费失败了,则可以重试,但还一直失败怎么办?是否要自动回滚整个流程?答案是人工介入。从工程实践角度来讲,这种整个流程自动回滚的代价是非常巨大的,不但实现起来很复杂,还会引入新的问题。比如自动回滚失败,又如何处理?对应这种发生概率极低的事件,采取人工处理会比实现一个高复杂的自动化回滚系统更加可靠,也更加简单。

弱一致性+基于状态的补偿

如果用最终一致性方案,因为是异步操作,如果库存扣减不及时会导致超卖,因此最终一致性的方案不可行;如果用TCC方案,则意味着一个用户请求要调用两次(Try和Confirm)订单服务、两次(Try和Confirm)库存服务,性能又达不到要求。如果用事务状态表,要写事务状态,也存在性能问题。

既要满足高并发,又要达到一致性,鱼和熊掌不能兼得。可以利用业务的特性,采用一种弱一致的方案。

比如对于电商的购物来讲,允许少卖,但不能超卖。

先扣库存再创建订单

扣库存 提交订单 调用方处理
成功 成功 返回成功
成功 失败 告诉用户失败,用户重试(扣库存+提交订单),可能会导致多扣库存
失败 不提交订单 告诉用户失败,用户重试(扣库存+提交订单),可能会导致多扣库存

先创建订单再扣库存

提交订单 扣库存 调用方处理
成功 成功 返回成功
成功 失败 告诉用户失败,用户重试(提交订单+扣库存),可能会导致多扣库存
失败 不提扣库存 告诉用户失败,用户重试(提交订单+扣库存)

也就是数据库中可能存在 库存记录无法对应订单,但不可能存在订单记录无法对应库存。

库存每扣一次,都会生成一条流水记录。这条记录的初始状态是“占用”,等订单支付成功后,会把状态改成“释放”。通过比对,得到库存系统的“占用又没有释放的库存流水”与订单系统的未支付的订单,就可以回收这些库存,同时把对应的订单取消。类似12306网站,过一定时间不支付,订单会取消,将库存释放。

对账

岂止事务有状态,系统中的各种数据对象都有状态,或者说都有各自完整的生命周期,同时数据与数据之间存在着关联关系。我们可以很好地利用这种完整的生命周期和数据之间的关联关系,来实现系统的一致性,这就是“对账”。

在前面的方案中,无论最终一致性,还是TCC、事务状态表,都是为了保证“过程的原子性”,也就是多个系统操作(或系统调用),要么全部成功,要么全部失败。但所有的“过程”都必然产生“结果”,过程是我们所说的“事务”,结果就是业务数据。一个过程如果部分执行成功、部分执行失败,则意味着结果是不完整的。从结果也可以反推出过程出了问题,从而对数据进行修补,这就是“对账”的思路!

假定从“已支付”到“下发给仓库”最多用1个小时;从“下发给仓库”到“出仓完成”最多用8个小时。意味着只要发现1个订单的状态过了1个小时之后还处于“已支付”状态,就认为订单下发没有成功,需要重新下发,也就是“重试”。同样,只要发现订单过了8个小时还未出仓,这时可能会发出报警,仓库的作业系统是否出了问题……诸如此类。

小结

  1. “最终一致性”是一种异步的方法,数据有一定延迟。异步就离不了队列,方法调用改为消息传递,方法调用成功转换为消息发送一定要成功,消费方一定要消费成功,实在是消费失败了就人工处理。
  2. TCC是一种同步方法,但TCC需要两个阶段,性能损耗较大。因为是同步操作,所以子服务成功与失败是明确知道的,调用方通过重试失败的子服务来确保一定成功,调用失败后不断重试,重试一定次数后同样转入人工处理。
  3. 弱一致性,有一个子服务失败则中断调用(已经成功的子服务“补偿”处理),整体重试。
  4. —“对账”是一个事后过程,相当于对 对关联服务的成功与失败放任不管了

其它

分布式系统事务一致性解决方案

以交易系统为例,看分布式事务架构的五大演进

关于分布式事务、两阶段提交协议、三阶提交协议

分布式事务的典型处理方式:2PC、TCC、异步确保和最大努力型