《Designing Data-Intensive Applic

2018-02-28  本文已影响62人  赤子心_d709

1.说明

事务,是一个把若干读写放入同一个逻辑单元的方式。
概念上,事务的读写要么全成功(commit),要么全失败(abort,rollback)。即使失败,程序也能安全的重试。
有了事务,处理错误会变得很简单,因为不用担心部分错误的问题。
有了事务,db也会有安全保障,应用程序可以忽视特定的错误场景以及并发的问题。

实际上,不是所有应用都需要事务,那么怎么知道自己需不需要事务,这就要理解事务能够提供的安全保证以及对应的代价。下面看一系列问题,如并发控制,多种竞争的触发以及db实现的隔离级别

2.概念

如今几乎所有的关系db以及部分非关系db都支持事务
部分非关系db默认提供分区,备份的支持,而这种场景下,事务则成了受害者。这些新生代的db放弃了食物或者提供了非常弱的保证。

如今关于分布式db以及事务的关系,有两种观点

第一种观点是事务制约了拓展性,任何大型系统应该抛弃事务来保证高可用性以及效率。
另一种观点认为,db必须提供事务的保证,完成对“有价值数据”的某些处理。
两种观点都夸张了。

实际上,所有技术选型都有优势以及制约,稍后会深入细节讲解事务的trade-off.

2.1 ACID

事务提供的安全保证,常称为ACID,即原子性,隔离性,一致性,持久性。这是1983年Andreas Reuter和 Theo Härder提出的。
然而,不同db对他们的实现各不一样,甚至对于I(隔离性)都有不同的理解。
不幸的是,ACID几乎成了一个市场用语。

Atomicity

广义来说,原子性表示一个东西不能够拆解的更细了。
在事务中,原子性表示:

所有事务的所有操作要么都成功,要么都失败。
不会因为某些原因(断电,网络中断)等原因造成数据处于部分成功,部分失败的状态。
这使得失败的事务可以进行重试,而不会使得数据出现duplicate或者错误。

Consistency

一致性这个词到处都在用

备份一致性(如最终一致性)
一致性hash
CAP理论(后续第9章再介绍)

然而在事务中,一致性表示:

在应用的层面上,数据处于一个正确的状态
ACID中,AID其实都是db的特性。然而C(一致性),是一个应用程序的性质
应用程序需要db的原子性和隔离性,来达到应用程序的一致性

比如一个会计系统,数据要保证收支平衡。这是程序自己保证的。
如果程序自己写入收支不平衡的错误数据,db并不能阻止。

Isolation

多个client同时访问db的同一份数据时,会出现并发竞争问题。
如下图实例,两个client获取当前值并进行+1操作


图1

由于出现了竞争,数据本该从42变成44,结果变成了43

隔离,表示同时执行的事务之间互不干扰。
db保证并发执行的事务的结果要和他们串行执行的结果一样.

实现中,串行化的隔离基本不用,因为代价很高。有些db如Oracle中会用快照隔离,是比串行化隔离弱一点的隔离级别,后面会讲

Durability

持久性是一个承诺,一旦事务成功提交,它所写的任何数据将不会丢失,即使有硬件故障或数据库崩溃。
在单节点数据库中,持久性通常意味着数据已写入非易失性存储(如硬盘驱动器或SSD)。它通常还需要写入日志,以便出现文件损坏时恢复工作。
在分布式数据库中,持久性可能意味着数据已成功复制到一些节点上。

当然,完美的持久性是不存在的,比如所有备份,硬盘同时被损坏。

2.2 单obj以及多obj的操作

ACID中的AI描述了一个事务中包含多个写时,db应该处理的事情

原子:提供要么全成功要么全失败的保证,不会出现中间状态
隔离:并发执行的事务互不影响,一个事务要么看到另一个事务的所有写,要么看不到任何写

上述定义假定多个事务在同时修改多个obj(记录)

下图以收发邮件为例,查找收件人未读的邮件,以及从邮箱中查找未读邮件的个数

图2,user2看到了user1的未提交的写
上图违背了事务的隔离性,是不满足ACID要求的。称之为脏读
多对象的事务请求 要求有一种方式决定哪些读写属于同一个请求,这可以通过client的tcp链接知道
在BEGIN TRANSACTION以及COMMIT之间的语句都被认为是同一个事务

很多非关系型db对于原子性支持不好(即使有multi operation)
会存在部分数据更新成功但是部分数据失败的情况

单obj的写

原子性能够用崩溃恢复日志实现
隔离性能用对象锁实现

有些db提供复杂的原子操作,利用自增操作解决图1中的read-modify-write的问题
类似的是CAS操作,即compare and set,这里不展开

这些操作十分有用,能够阻止多个client同时写一个obj时出现的lost updates问题,后面会介绍

对于 多obj的写 的需求

现状:
很多分布式存储放弃了多obj的事务,因为在分区中太难实现了,另外还会影响性能。

是否需要多obj的写:
很多场景单object操作就够了,但是也有场景要处理多object,即外键,二级索引等等。
这些场景当然也可以不用事务实现,但是缺乏了原子性和隔离性会有下述后果
缺乏原子性,错误处理变得更复杂。
缺乏隔离性,会出现并发问题。下一节会介绍Weak Isolation levels。

2.3 处理错误以及丢弃错误

事务的特性决定了 它不会出现执行了一半的情况。

现状:
不是所有系统遵从这个规矩,比如在无leader模型中,会尽量执行,如果遇到了错误他们也不会回滚,由应用程序自行从错误中恢复。
错误最终会发生,但是很多开发缺盲目乐观,忽视了对于错误的错综复杂的处理。

如何处理:
事务出错之后,重试是一种简单有效的方法,但是并不完美

1.执行成功但是网络原因导致重试,会执行两次
2.如果负载压力过大,重试会加重负载
3.短暂的错误之后重试才有用(如死锁,网络中断等),永久的错误是不值得重试的
4.如果事务对db以外的部分有影响,如发送邮件,这个影响可能会持续,即使事务已经被丢弃了
5.如果client重试失败了,所有写的数据都会丢失

名词总结

主要涉及名词如下,在本章内容都会有详细介绍

ACID

快照隔离
脏读

read-modify-write
lost updates
Weak Isolation levels

思考

无leader模式是不支持事务的:
因为无法回滚,保证不了原子性

图1的例子中,两个事务之间怎么就互相干扰了呢?
可以理解为,第一个事务开始执行却还没有commit时,第二个事务也处于了同样的状态。
出现了race condition

错误的解决方式是重试,重试的代价如何
文章上面有写

总结

本章介绍了事务以及ACID的定义
对于单obj以及多obj的操作,现状进行了一定的展开
对于错误处理以及重试的代价进行了分析

暂时不知道的问题

是否有分布式事务
事务和分区,备份是怎样的结合,能全部结合还是部分结合
zk的multi op与事务的关系,以及支持

refer

https://www.jianshu.com/p/a84e4f41a2aa

上一篇下一篇

猜你喜欢

热点阅读