事务与分布式事务

很多同学在开发中已经不自觉的接触了很多事务相关的代码(尤其是在数据库操作中),但是事务究竟是做什么的,有没有必要必须这么操作?

一段典型的代码如下:

db.beginTransaction();
try {// do some CRUD operationdb.commit();
} catch {//Error in between database transaction db.rollback(); } finally { db.endTransaction(); } 

从这段代码可以更直观的感受一下 “事务” 这个抽象的概念,那么事务是干什么用的呢?

事务(Transaction)


在wiki的解释中,事务是一组单元化的操作,这组操作可以保证要么全部成功,要么全部失败(只要有一个失败的操作,就会把其他已经成功的操作回滚)。这样的解释还是不够直观,看下面一个经典的例子。

假设有两个银行账户A和B,现在A要给B转10块钱,也就是转账。在银行系统中A和B是两个独立的账户,所以转账操作会被分解:

  1. 从A的账户中扣掉10块钱
  2. 在B的账户中添加10块钱

那么问题就来了,如果成功的在A账户中扣掉了钱,但是没有在B中加钱怎么办?或者A中没有成功扣款,B中却加了钱怎么办?这些可能性都是有的,比如突然断电、系统崩溃或者A账户本来就没有钱。所以无论上面哪一张情况发生,都是不应该的。

解决上面问题的一种简单方法就是事务 - 要么两个操作都成功返回一个成功的结果,否则把所有操作回滚,返回一个失败的结果。所谓回滚就是如果从A中扣钱成功但B中加钱失败的话,那么把A中扣得钱再还回去。

注:事务的英文Transaction其实就是交易的意思

事务操作的基本步骤概括如下:

  1. 开始事务
  2. 执行一系列的数据库操作
  3. 如果没有错误发生,那么提交事务,返回成功
  4. 如果有错误发生,那么回滚事务,返回失败

同时事务发展出几个基本原则 - ACID:

  1. Atomicity 原子性,要不成功要么失败,部分成功是不可以的
  2. Consistency 一致性,在事务开始之前和事务结束以后,数据库的完整性没有被破坏(一致性一般是由应用来指定的)
  3. Isolation 隔离性,当一个事务正在进行的时候,假设没有第二个事务在进行(并发,如果真的发生了,系统需要保证隔离的程度)
  4. Durability 持久性,事务完成后,该事务对数据库所作的更改便持久地保存在数据库之中(防止系统崩溃)

这几个原则构成了单一数据源事务的基本原则。

代码实现


由于事务的这些特点,所以在事务相关的代码中,基本都是类似的风格:

db.beginTransaction();
try {// do some CRUD operationdb.commit();
} catch {//Error in between database transaction db.rollback(); } finally { db.endTransaction(); } 

这就是我们经常见到的代码(无论哪种语言)。这种相对比较固定的模板导致一种“声明式”事务的产生,在Spring中会经常见到,所谓的“声明式”大概表现如下:

@Transactional
public void insertXXX() { // do something... } 

在方法的声明上,通过 @Transactional 注解把一个方法标注为支持事务的,那么在执行的时候,容器会自动给这个方法围绕上面的代码块。

事务的实现原理


在开始的时候,我们说事务可以保证操作的ACID原则,那么事务究竟是如何保证这些原则的?db.beginTransaction()db.commit()db.rollback()db.endTransaction()究竟干了什么事,如果这些操作本身也失败了怎么办?

实现事务功能的系统通常叫 “TransactionProcessingMonitor” 或 “TransactionManager”,这些系统通常会被打包进数据库引擎中,在分布式系统中也会作为一个独立的模块存在。

解决ACID问题的两大技术点是:

  1. 预写日志(Write-ahead logging) 保证原子性和持久性
  2. (locking) 保证各隔离性

这里并没有提到一致性,这是因为一致性是应用相关的话题,它的定义一般由业务系统来定义 - 什么样的状态才是一致的?而实现一致性的代码通常在业务层逻辑的代码中得以体现。

是大家熟悉的一个话题,在并发环境中通过读写锁来保证操作的互斥性,没有太多神奇的东西。根据隔离程度不同,锁的运用也不同,可能会产生一些问题,具体可以查看 Isolation (database systems)。

本文比较感兴趣的是预写日志。因为在数据库的复杂数据结构中更新数据是一个比较慢的操作,保证这种操作的原子性和持久性是很困难的。预写日志的工作模式是这样的:在任何事务操作发生之前,先把所有的变化写入一个日志文件并持久化,然后再开始真实的写数据库操作。如果在接下来的操作中系统崩溃,那么我们可以在系统恢复之后检查日志和数据库中的记录,来决定是继续执行完成未尽的任务还是回滚操作 - 把数据库还原到这次事务之前的一个状态。

预写日志一般采用简单顺序日志(sequential file)的写入格式,这样日志写入速度可以很快。这点很重要,因为一般事务操作的吞吐量往往受到日志系统速度的限制。日志的格式会同时记录redo和undo的信息。

分布式事务


在大型应用开发中,经常要做业务拆分,把原来的单一架构的应用拆分成不同的服务。不同的服务之间松耦合可以很好的解决业务耦合问题,但是这样也会带来事务处理的问题,如果一个操作会同时写入两个数据库,那么如何保证这两个写入的一致性?

单一数据库可以通过ACID原则保证自己的事务处理,但是如果有两个不同的数据库,如何保证针对这两个数据库的事务都成功呢?在 JavaEE 规范中使用 2PC (2 Phase Commit, 两阶段提交) 来处理跨 DB 环境下的事务问题。

简单来说J2EE的2PC协议是这样的,先把事物请求发送给中间协调器,协调器负责各个数据源的事物处理。处理过程分两个阶段:

  1. 投票阶段
    协调器把事物请求发送给各个数据源,数据源负责各自的事物处理。
  2. 完成阶段
    协调器根据各个数据源的返回结果,决定是处理成功或者失败,只要有一个结果是失败的,那么会回滚所有数据源的事物处理。

这种处理方式,实际上是一个放大版的ACID原则。但是在分布式环境下,2PC 是反可伸缩模式,在事务处理过程中,参与者需要一直持有资源直到整个分布式事务结束。这样,当业务规模达到千万级以上时,2PC 的局限性就越来越明显,系统可伸缩性会变得很差。

CAP


在过去Inktomi的Eric Brewer曾提出过分布式系统的一种猜想(conjecture),在分布式系统中的三项重要指标:

  • Consistency 一致性
  • Availability 可用性
  • Partition-tolerance 分区容忍度

是不能同时成立的 - 在任意时刻,只有两项能同时成立。对于高流量的网站来说,为了提高系统伸缩性,一般都会牺牲一致性。

BASE


BASE是一种尝试通过牺牲一部分一致性而达到高可用性的原则,ACID原则中要求系统的每个操作之后都是连续的,但是BASE认为系统是可容忍局部的/短时间的不一致。

在基于ACID的事务中,事务是简单可靠的,为了达成这种效果,往往会造成耗尽整个系统的资源造成整体不可用。而BASE的实现是复杂的和业务紧密相关的。BASE 原则体现在(采用这种原则意味着放弃ACID):

  • 基本可用(Basically Available)
  • 软状态(Soft state)
  • 最终一致(Eventually consistent)

这是一组非常抽象的概念,通过这组原则你不能领会任何可行的具体的系统设计方案。BASE并没有指明任何方案,只是在告诉你 - 其实还可以这么搞!

下面看一下简单的例子(从这里偷来的):假如有一个系统可以在上面买卖物品,可以设计着这样的表结构:

user and transaction

在这个系统中,如果产生了一项交易,那么会现在Transaction表中记下一条记录,然后在买家和卖家表中分别更新记录。基于ACID原则的事务代码是这样的:

acid

这里面的三条SQL操作会分别更新三个不同的数据库,在ACID系统中会使用2PC实现。如果从BASE的角度来考虑可以这么设计:

base

对于整个系统来说Transaction表才是真正有意义的,User表中的相关数据可以认为是为了系统性能而设计的缓存(这样不必查Transaction表即可或许相关的数据)。所以可以设计出上面这种事务模型,这种模型的潜在问题是,如果对于User表的操作失败了,那么在用户端看到的结果是不准确的。现在我们的系统已经出现了不一致的问题,如果我们提前告知用户,他看到的数据是粗略的估计,那么这个问题在业务上算是解决了。

但是如果我们不能容忍这种可能会造成永久的不一致,那么该如何解决问题呢?答案在消息系统 - 可靠的消息系统。下面是改进版:

base and mq

在这种设计中,第一段代码保证了Transaction和消息持久化的事务性,然后在消息处理系统中在分别更新User表的数据。并且在处理消息的时候,仅在事务成功的时候才移除消息,这样可以保证User可以成功的更新。因为用到了消息系统,所以必然存在消息的延迟问题,而这正式前面说的 - 牺牲一部分的一致性(User和Transaction表不是同步更新的)。但是一旦消息被成功处理,我们最终会达成一致的状态 - 即最终一致。

看完了这个例子,对于BASE也仅仅是初步的认识,在真实的业务系统中还需要根据自己业务的特色设计相应的解决方案。

注1:分布式事务确实牛逼,写作过程中深感压力,如果不足不对的地方,还请高手多多指正。
注2:对于最开始那个转账的例子,用BASE思想的实现现在还不方便说,因为这是蚂蚁内部的资料。

参考:

  • ACID
  • Transaction Processing Cheat Sheet
  • Database transaction
  • Spring事务配置的五种方式
  • Transaction processing system
  • Write-ahead logging
  • 牺牲一致性来换取分布式架构的可伸缩性
  • 可伸缩性最佳实践:来自eBay的经验
  • Base: An Acid Alternative



作者:ntop
链接:https://www.jianshu.com/p/d322cba3add4

转载于:https://www.cnblogs.com/miracle77hp/p/10170118.html

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

platform 设备驱动实验

目录Linux 驱动的分离与分层驱动的分隔与分离驱动的分层platform 平台驱动模型简介platform 总线platform 驱动platform 设备硬件原理图分析试验程序编写platform 设备与驱动程序编写测试APP 编写54.5 运行测试编译驱动程序和测试APP运行测试我们在前面几章编写的设备驱动都非常…

linux 恢复数据

对于很多 Linux 的用户来说,可能有一个问题一直都非常头疼:对于那些不小心删除的数据来说,怎样才能恢复出来呢?大家知道,在 Windows 系统上,回收站中保存了最近使用资源管理器时删除的文件。即便是对于那些…

设备树下的platform 驱动编写

目录设备树下的platform 驱动简介硬件原理图分析实验程序编写修改设备树文件platform 驱动程序编写编写测试APP运行测试编译驱动程序和测试APP运行测试上一章我们详细的讲解了Linux 下的驱动分离与分层,以及总线、设备和驱动这样的驱动框架。基于总线、设备和驱动这…

回归测试——游戏版本发布前的最后关口

回归测试在软件开发中很常用,在游戏测试中也是很重要的一个环节,它起着保障作用。可能各个公司游戏项目的回归测试流程不大相同,我就拿我参与的游戏项目来举例。在一个迭代的过程中,最后收尾动作就是回归测试,简单说就…

Linux 自带的LED 灯驱动实验

目录Linux 内核自带LED 驱动使能Linux 内核自带LED 驱动简介LED 灯驱动框架分析module_platform_driver 函数简析gpio_led_probe 函数简析设备树节点编写运行测试前面我们都是自己编写LED 灯驱动,其实像LED 灯这样非常基础的设备驱动,Linux 内核已经集成…

虚拟硬盘VHD的程式化挂载方式

2019独角兽企业重金招聘Python工程师标准>>> 微软推出的虚拟硬盘vhd同vmdk,qcow2等一样,是一种磁盘镜像格式。磁盘镜像一般多用于虚拟化计算,但微软支持将Win7/Win8/Win2012等直接安装进vhd文件中,并且不通过任何虚拟机&#xff0…

Cs231n课堂内容记录-Lecture 5 卷积神经网络介绍

Lecture 5 CNN 课堂笔记参见:https://zhuanlan.zhihu.com/p/22038289?referintelligentunit 不错的总结笔记:https://blog.csdn.net/sugar_girl/article/details/79108709 1.卷积核步长公式:(N-F2*padding)/stride1ne…

Linux MISC 驱动实验

目录MISC 设备驱动简介硬件原理图分析实验程序编写修改设备树beep 驱动程序编写编写测试APP运行测试编译驱动程序和测试APP运行测试misc 的意思是混合、杂项的,因此MISC 驱动也叫做杂项驱动,也就是当我们板子上的某些外设无法进行分类的时候就可以使用MI…

PHP的session阻塞问题

2019独角兽企业重金招聘Python工程师标准>>> 通过phpinfo()可以看到php支持的session存储方式有: 可以看出session的存储方式有文件、用户自定义、memcache存储方式 在文件的存储方式用存在session阻塞的问题,该种阻塞存在于同一个浏览器执行…

Linux INPUT 子系统实验

目录input 子系统input 子系统简input 驱动编写流程input_event 结构体硬件原理图分析实验程序编写修改设备树文件按键input 驱动程序编写编写测试APP运行测试编译驱动程序和测试APP运行测试Linux 自带按键驱动程序的使用自带按键驱动程序源码简析自带按键驱动程序的使用按键、…

django项目中settings.py文件中路径设置问题

2019独角兽企业重金招聘Python工程师标准>>> 首先,__file__表示当前模块的名字。 将下列代码写入os.path.py文件中,存放在桌面上 """ import os print __file__ """ win R 开启一个 CMD,使用pytho…

Linux LCD 驱动实验

目录Linux 下LCD 驱动简析1 Framebuffer 设备LCD 驱动简析硬件原理图分析LCD 驱动程序编写运行测试LCD 屏幕基本测试设置LCD 作为终端控制台LCD 背光调节从LCD自动关闭解决方法LCD 是很常用的一个外设,在裸机篇中我们讲解了如何编写LCD 裸机驱动,在Linux…

一个老兵的linux学习和面试经验分享

特别说明:本文为约9个月前老男孩linux培训内部师兄给师弟的经验分享,经过该同学同意,特此分享给所有博友。学习和面试经验分享大家好,非常高兴能在这里给大家分享学习和面试的经验,同时也非常感谢老男孩老师给我这次机…

Linux RTC 驱动实验

目录Linux 内核RTC 驱动简介I.MX6U 内部RTC 驱动分析RTC 时间查看与设置RTC 也就是实时时钟,用于记录当前系统时间,对于Linux 系统而言时间是非常重要的,就和我们使用Windows 电脑或手机查看时间一样,我们在使用Linux 设备的时候也…

opencv及相机相关6

下午调试了下,现在可以实现在arm板上打开相机并实时显示,但是人脸检测暂时还没有实现,猜想是因为opencv的原因。明天接着调试。今天整明白一件事,首先在linux系统下,g编译器当cpp文件较少时,编译程序时需要…

Linux I2C 驱动实验

目录Linux I2C 驱动框架简介I2C 总线驱动I2C 设备驱动I2C 设备和驱动匹配过程I.MX6U 的I2C 适配器驱动分析I2C 设备驱动编写流程I2C 设备信息描述I2C 设备数据收发处理流程硬件原理图分析实验程序编写修改设备树AP3216C 驱动编写编写测试APP运行测试编译驱动程序和测试APP运行测…

NXP(I.MX6uLL)DDR3实验——DDR3重要时间参数、时钟配置与原理图简析

目录DDR3 内存时间参数传输速率tRCD 参数CL 参数AL 参数tRC 参数tRAS 参数I.MX6U MMDC内存控制器简介MMDC内存控制器简介MMDC内存控制器信号引脚MMDC内存控制器时钟源ALPHA开发板核心板DDR3L原理图DDR3 内存时间参数 大家在购买DDR3 内存的时候通常会重点观察几个常用的时间参…

[elk]elasticsearch dsl语句

例子1 统计1,有唱歌兴趣的 2,按年龄分组 3,求每组平均年龄 4,按平均年龄降序排序 sql转为dsl例子 # 每种型号车的颜色数 > 1的 SELECT model,COUNT(DISTINCT color) color_count FROM cars GROUP BY model HAVING color_count > 1 ORDE…

NXP(I.MX6uLL)DDR3实验——DDR3初始化、校准、超频测试

DDR3L 初始化与测试ddr_stress_tester简介(DDR压力测试工具)DDR3L驱动配置DDR3L校准DDR3L超频测试DDR3L驱动总结ddr_stress_tester简介(DDR压力测试工具) NXP 提供了一个非常好用的DDR 初始化工具,叫做ddr_stress_tester。此工具已经放到了开发板光盘中&#xff0c…