先来回顾下,之前的几个一致性的概念。
首先是STRICT CONSISTENCY,就是要求所有的操作都严格的按照机器上的WALL CLOCK来排序。那么所有并行的进程的操作都会有序。这在分布式的情况下是做不到的。
和这个最接近的是SEQUENCE CONSISTENCY。它的经典实现是IVY,用自定义的TOTAL ORDER来取代WALL LOCK。我们只要认为所有机器看到的这个顺序是一样的,就是符合SEQUENCE CONSISTENCY。
下一级是 RELEASE CONSISTENCY,他确保的是锁的释放和获取之间是被SEQUENCE CONSISTENCY 保护的。而没有锁的地方顺序是可以不一样的。
EVENTUAL CONSISTENCY, 是为了性能考虑,而引入。它所保证的是你可能看到的结果可能是旧的,但未来某个时刻会得到新的。
下面我们就来讨论如果一台机器挂了怎么办?
机器挂了的最大问题是状态会丢失,所以我们要做的是保证重启之后状态和原来一样并且挂的这个事使得整个执行进入一个不合理的状态。
不合理的状态就是个中间状态,不是ALL OR NOTHING。 是操作了一半留个烂摊子的状态。这个中间的状态是非法的。
下面举个1000块的转账的例子。为了性能考虑,一开始都是缓存在内存里,最后写入磁盘。
这个例子会发生A的钱被减掉,B却没拿到。
第一种思路是用备份的页再上面改完,然后用一个指针转移(这个操作为原子)来实现原子效果。
这种思路有2个问题。
1.可能原子操作点不是很好找
2.TX可能不是由一个人完成的。
解决思路是LOG
这个LOG 可以在旧状态时用来REDO,恢复出新状态。
也可以在新状态时UNDO,恢复成旧的。
第一种设计,使用一个LOG文件来记入所有TX。LOG是用APPEND的方式追加在最后。
为什么要使用一个文件,而不是每个TX一个LOG 呢?
好处是顺序写比较快。如果是多个文件,几个TX并行,就变成了随机写会慢很多。
TX COMMIT之后怎么实现的?
WAL:写内存前先保证LOG在DISK上写完了。
在一系列操作完成后,要做COMMIT ACTION,写COMMIT标志进LOG
发生问题,如何RECOVER。
MEMORY全丢失,可以去看LOG。
从后向前扫。首先判断TX提交还是没提交。有标记的做REDO记号,没标记的丢弃。
随后根据有标记的REDO,从前向后重做。
这样全部重做,很慢,所以我们需要CHECK POINT。可以避免LONG TRANSACTION FLOW。
当一个时间点,没有TX在做了。我可以加入一个CHECK POINT标志。代表之前的都OK了,那我下次REDO的时候,可以直接从CHECK POINT 开始。
但是这个时间点很难找,怎么办?
用当前没有正在执行的ACTION。
下面看个例子。
现在CHECK POINT 开始RECOVERY, 会记录INPROGESS TX LOG 的位置。
会有T2 和 T4的LOG。
因为1是完成的TX。 2和4 是IN PROGESS的。
这个CASE是一些操作先放内存,每次CHECKPOINT 的时候去写DISK。
红色框是需要REDO, 绿色框是需要UNDO
还有一种策略,是在完成TX的时候,才去把一些结果写进CHECK POINT。没有完成就什么都不写。这样的好处是不需要UNDO LOG。这种策略是REDO ONLY 的策略。
我们看下转账的例子,底层的LOG在写什么?
这个转账是A 有3000块,B有2000块,A向B 转1000
C有10块,D有0块, C向D转10 块。
然后找到CHECK POINT 开始做RECOVERY
因为AB的转账,已经COMMIT了。所以在CHECK POINT的结果都写进DISK了。但是C 和D都是0.
首先要把没完成的TX UNDO。所以根据CHECK POINT,找到C = C-10,来做UNDO。 C = C +10;
就UNDO好了。 随后把T1 COMMIT 的log 在CHECK POINT之后做REDO。
为啥要同时做UNDO REDO LOG?
如果一个TX很长,如果只有REDO,那么TX的操作都去不了DISK,需要很多内存去缓存。然后恢复需要全部REDO很费时间。
如果只有UNDO,那就每个操作都要写进去,就更慢了。
如果没有LONG TX,是可以用其中一种LOG的。REDO + CHECKPOINT。
这边就是A 会被FLUSH,因为TX1 COMMIT了。B 会被REDO。 C 不会被FLUSH。