2.3 事务层
事务务支持ACID,协调并发操作。
**概要
--读写(第一阶段)
--提交(第二阶段)
--清理(异步的,第三阶段)
**技术细节和组件
--时间和混合逻辑时钟
--时间戳缓存
--client.Txn和TxnCoordSender
--事务记录
--Write Intents(写意向)
--隔离级别
--事务冲突
**与其他层的交互
--事务层和SQL层
--事务层和分发层
2.3.1 概要
CockroachDB相信一致性是事务最重要的特性,没有它,开发者无法建立可靠的工具。交易会被潜在的微小的无法捕捉的异常影响。要保证一致性。CockroachDB
实现了对ACID的全部支持,其中,重要的是实现事务处理的语句,如commit mode 为在每条语句后添加commit 语句。
对于使用事务的例子,请查看事务文档
CockroachDB可以保证涵盖整个集群,包括cross-range,cross-table事务。通过两阶段提交,提高处理的准确性。
**读写(第一阶段)
1) 写
事务执行写操作,并非直接写入磁盘中,而是创建两个事件去协调分布式事务。
--当一个写发生时,事务记录存储在一个range上,并记录事务当前状态(以pending开始,以commited或aborted结束)
--写意向(write intends),是对于一个事务所有的写操作,临时的未提交的状态,与多版本并发管理(MVCC)相同,同时实现了一个指向集群中事务记录的指针。
当写意向发生,cockroachDB检查最新的提交值,如果存在,事务重新开始。对相同keys存在多个写意向,需要解决事务冲突。
如果事务因其他原因失败,例如未通过SQL约束检查,事务将异常终止(aborted)。
2)读
如果一个事务没有异常终止(aborted),事务层开始执行读操作。满足标准的MVCC,只读事务,没有任何问题。但如果有任何写意向,必须通过事务冲突来解决。
**提交(第二阶段)
CockroachDB检查正在执行的事务记录,查看它是否被(aborted),如果有,重启事务。如果事务通过这些检查,将被移动到commited,向客户端返回事务成功。同时,客户端空闲,并开始向集群发送更多请求。
**清理(异步,第三阶段)
在事务被分解,所有的写意向会重新被分解。合作节点会跟踪所有写入的key,获得值或者。。
--通过移动事务记录的指针,将写意向分解为MVCC的值
--删除写意向
这是一个简单优化: 如果将要的操作遭遇一个写意向,需要检查事务记录,通过检查事务记录状态判断是否有操作可以分解或者移动。
**与其他层互动
与cockroachDB其他层的关系,事务层
--接收来自SQL层的KV值
--控制KV操作流发送到分发层
2.3.2 技术细节和组件
**时间和逻辑混合时钟
在分布式系统中,排序和因果性是最困难的问题。完全依赖于raft一致性算法实现可序列化,是无法满足读数据要求的。为了优化读的实现,cockroachDB实现了逻辑混合时钟(HLC),由一个物理组件(接近本地挂钟时间)和逻辑组件(使用同样的物理组件去区分事件)组成。HLC时间大于等于挂钟时间,你可以在HLC论文中查看具体细节
https://www.cse.buffalo.edu//tech-reports/2014-04.pdf
在事务中,网关节点使用HLC时间为事务打时间戳,无论何时事务的时间戳被提及,都会是一个HLC值。这个时间戳被用于跟踪值的版本(使用MVCC),提供事务隔离级别保证。
当节点发送请求到其他节点,包括本地HLC时间戳(包括物理组件和逻辑组件)。当节点接受到请求,将获得发送者事件的本地HLC时间戳,保证所有的数据在单个节点的读写时间戳下一个HLC时间。
--最大时钟偏移执行
通过保证最大时钟偏移,保证分布式节点的正确性。CockroachDB依赖于时钟同步,节点通过运行一个版本的Marzullo’s algorithm算法,测量集群的最大时钟偏移。如果任一节点的最大时钟偏移超过了指定值,提交自杀,防止集群中潜在的创建一致性问题。
对于引起磁盘的大时钟偏移的更多细节,请查看
当节点时钟没有正确同步会发生什么?
CockroachDB需要适度准确的时间保证数据的一致性,在每个节点执行NTP或者其他时钟同步软件是重要的。
默认,cockroachDB最大的允许500ms的时钟偏移。当一个节点检测到与其他节点的时钟偏移为最大允许时钟的一半或更多,该节点将自发停止。利用最大允许时间偏移(500ms),可序列化一致性之下不保证陈旧的读和写偏序发生。通过在每个节点的NTP时钟或者其他同步时钟软件,超过最大偏移或者出现异常的风险较小。即使完善的硬件,如果没有运行同步软件,慢的时钟漂移是普遍的,cockroachDB解决了这个问题。
一个较为罕见的现象是,在节点检测前,一个节点的时钟突然跳变,超过最大偏移。虽然可能性较少,但是确实可能发生,例如:当cockroachDB在VM中运行,VM管理程序决定将虚拟机移动到不同时间的其他硬件,这时,有一个小的窗口时间,节点时钟不同步,节点自发停止。在这段时间客户端有可能读陈旧的数据或者写陈旧的数据。
**时间戳缓存
为了提供序列化,无论何时读操作读一个值,会保存操作的时间戳,这个显示了读取值得高水位。
无论何时,一个写操作发生,时间戳检查时间戳缓存,如果一个时间戳比当前的最新时间小,将事务移动这个时间戳到最新的时间,假设可序列化事务,这将导致重启第二阶段事务。
**Client.Txn和TxnCoordSender
在SQL层架构的概要中说过,cockroachDB将所有的SQL语句转化为KV操作,数据是如何最后存储和获取的。
所有来自于SQL层的KV操作使用client.Txn,这是cockroachDB的KV层的事务接口,像我们讨论的,所有的语句被看做事务,所有的语句使用这个接口。
但是,client.Txn只是TxnCoordSender的一个封装,在我们的代码中发挥很重要的作用:
--处理事务的状态。在一个事务启动后,TxnCoordSender开始发送一个异步的心跳消息给事务的事务记录,这个信号表示是keep alive状态,如果TxnCoordSender的心跳停止,事务记录转移到aborted状态。
--跟踪事务的每一个写key或者key range
--当事务提交或者aborted,清理积压的事务写意向。所有的请求都是事务的一部分,都通过TxnCoordSender处理所有的写意向,优先清理进程。
在启动记账之后,请求传递到分发层的DistSender。
**事务记录
当事务开始,TxnCoordSender写一个在range上事务记录,涵盖第一个被事务修改的key。如上所述,事务记录向系统提供可信的事务状态。
事务记录使用如下方式表达一个事务:
--pending:所有值的初始状态,表示写意向(write intent)事务还在进程中。
--commited: 一旦事务完成,将变为commited状态
--aborted:如果事务失败或者被客户端异常终止,将移动到这个状态
已提交的事务记录保持,直到所有的写意向转化为MVCC值。对于一个aborted事务,事务记录可以在任何时刻删除。如果事务aborted,cockroach将失去事务记录。
**写意向(write intent)
CockroachDB的值不是直接写入存储层,而是写一个临时的状态,称作写意向。这是多版本并发管理的值很重要,即MVCC,在存储层有更深层的解释,这是一个额外的值,用于识别事务记录所属的。无论何时操作有一个write intent(即MVCC值),查询事务记录状态,确定如何对待write intent。
--解析写意向
一个操作遇到一个key的write intent,试图解析,解析结果依赖于写意向的事务记录:
Commit:操作读write intent,通过转移writer intent的指针,将其转化为MVCC的值。
Aborted:写意向被忽略并删除
Pending:存在事务冲突的信号,必须被解析。
**隔离级别:
ACID事务是一种隔离,保证并行控制,最终保证事务的一致性。cockroachDB意在保证数据库的高一致性,提供了两种隔离级别。
--serializablesnapshot isolation(序列化快照隔离)事务是cockroachDB默认的(等同于ANSI SQL的序列化隔离机制,是四种标准隔离级别Read uncommitted、Read committed、Repeatable read、Serializable中最高的)。这种隔离级别不允许数据的任何异常,拒绝移动事务的时间戳,终止事务如果时间戳移动。保证数据是可序列化的。
--快照隔离级别事务保证正确性,同时提高高竞争工作负载下的表现。在存在事务竞争的情况下,允许事务时间戳移动,允许写偏序异常发生。
**事务冲突
CockroachDB允许几种类型的冲突:
--写/写:当两个pending事务对同一个key创建write intents
--写/读:当一个读遇到一个存在的write intents时间戳小于自己的。
为了是理解更简单,将第一个事务叫TxnA,作为遇到write intent的事务叫做TxnB.
CockroachDB执行以下步骤,直到其中一个事务aborted,时间戳移动,或者添加到pushTxnQueue。
1) 如果一个事务有明确的优先级如(high或者low),更低优先级的事务会被ahorted.
2) TxnB试图将TxnA的时间戳向前移动
只能在TxnA使用快照隔离级别,TxnB的操作读操作,有可能,引起写偏序异常
3) TxnB加入PushTxnQueue,等待TxnA完成
--pushTxnQueue
PushTxnQueue跟踪所有的事务,不能推进一个事务遇到的写,必须等待阻塞事务完成,才能继续执行。
PushTxnQueue结构式一个blocking事务ID的map 列表,例如
TxnA -> txn1,txn2
TxnB -> txn3, txn4,txn5
重要的是,所有的活动都发生在单个节点,是涵盖事务记录的range的raft组的leader。
一旦一个事务被解析,通过commit或者abort,一个信号发送到PushTxnQueue,使所有被分析事务阻塞的事务,开始执行。
被阻塞的事务检查他们自己的事务状态,确定自己是active的。如果被阻塞的事务被aborted,可以容易的移除。
如果一个事务间存在一个死锁(即每个都被其他的write intents阻塞),其中一个事务被随机的aborted。在上面的例子中,如果TxnA阻塞了TxnB在key1上,事务TxnB阻塞了事务TxnA在key2上。
2.3.3 与其他层的交互
**事务层和SQL层
事务层接收来自SQL层planNodes执行的KV操作
**事务层和分发层
TxnCoordSender发送KV请求到分发层的DistSender.