标签:
原子意为不可再分、组成万物的基本元素。“不可再分”,在现代科学面前,略显尴尬,但在表达其本意时,大家都能领会其神,这真是极好的。
计算机科学领域的原子性,引申为一系列的操作,要么全部成功,要么全部失败,从“全部失败”(一系列操作都没有开始执行)的初始状态到“全部成功”(所有操作全部执行完毕)的之间再无其他状态。
在基本的原子操作之上,构建更大尺度的原子操作,是本文要探讨的主题。
为了加深原子性的印象,试举几个栗子。
硬件提供的功能就是一堆原始的积木,软件只能基于这些原始积木,搭建组合出更多品类、更大规模的积木(软件实现更丰富的功能),或者直接从最开始就提供这搭建好的积木(硬件直接实现更丰富的功能)。
在绝大多数场景下软硬件都是可以互相替代、不分你我的。然而在最基础最底层的地方,必然是硬件在支撑,颇有点唯物主义的意味。
众所周知,基于冯氏理论的计算机都建立在离散的0/1之上,0/1的栖身之所,就是“位”(bit)。那么可以先假设在“位”上的写操作是最基本的原子操作了。
在此基础上如何构建更多位上写操作的原子性呢?
1Byte为8bit,1Byte的写如果在硬件层面以8bit并行去写,以现在的工艺水平,应该可以做到要么8bit全部写成功,要么8bit全部没有写成功,而不至于其中1bit写成功,而另外7bit写失败。
之所以加上“应该”,实在是我瞎猜,不服来咬我啊。如果硬件层面做不到比1bit更多,那么现代软件工程大厦的基础就不牢靠。
对应到具体的硬件介质:
有了硬件层面对原子性的基础支持,是否就可以用软件的手段构建出任意规模的原子操作了呢?如果可以,硬件层面的支持至少是多少,1b?2b?还是1B,或1扇区(512B)?
我的答案是,至少1b。为什么是1b而不是2b?是离奇?是阴谋?还是巧合?抑或是小龙虾扯着鸡毛掸?
这要从软件如何实现的基本思想出发去分析,软件去实现的规模,必然跨越了多个硬件层面的原子操作,那么问题又来了:
从硬件层面去看,软件层面的原子性显然是个笑话。但是在软件层面,我们可以做一点手脚,使得观察这个状态的观察者看不到中间状态。因为观察这个结果是否原子性的过程仍然在软件可控的范围内(有点read repair的意思)。回到上面的问题:
先从最简单的开始,只考虑单磁盘写操作的原子性。
假设磁盘提供了一个在1b上的写操作原子性,如何实现NB(N Bytes,任意多字节,如1扇区则为512Bytes)的写操作原子性呢?
2种状态,0:终态(非中间态),1:中间态。
首先,借助上帝之手,将磁盘的状态位(1b),初始化为终态0,数据位(N Bytes)初始化为初始数据。
然后,Write(data,N Bytes)的操作来了,要么全部成功要么全部失败,结果不出意料写完N/2 Bytes的时候断电了,怎么对被覆写的N/2和剩下没写成功的另外N/2交待呢?考虑到后果的严重性,显然不能直接就开写,不然一半新一半旧,简直吓尿了。
这时正确的做法是:
可以看出以上的关键点就是提前备份可能被覆写的数据,记录这个中间状态,利用幂等操作不断重试,以此来对抗各种天灾人祸,直到最终成功,或者选择放弃当前写操作,用备份好的数据还原被写坏的原数据。从宏观的角度去看,最终实现了写N Bytes的原子写操作。这里消耗了同有效数据一样的空间作为备份空间,这就是原子性的代价了。
异构就是指操作1是在某磁盘写上数据,操作2是调用某个黑盒接口执行一个神秘写操作(如给月球钻个孔),...
这样一系列操作,要么全部成功,要么全部失败。貌似步子迈得有点大,扯到事务的蛋了。
这一批操作,得先有一个唯一的标识——事务ID,这个ID关联一个状态,状态有未开始、执行中、正常完成、回滚完成四种状态(为什么是4种?那不就要2b了吗?事实上,“未开始”、“正常完成”、“回滚完成”这3种状态,而单纯从实现原子性的角度而言,可以统一归为非中间态。)
步骤如下: 1. 提前保存操作过程中可能会修改的数据,用于回滚 2. 修改状态为“执行中” 3. 依次执行各操作 4. 如果最终成功状态修改为“已完成”;否则,用第1步保存的数据开始回滚,回滚完成后状态修改为“回滚完成”。
这里一个重要的前提就是每个操作都是幂等的,即执行一次和执行多次的效果等价。
1属于准备阶段,这一阶段如果失败可直接弃之。第3步如果某一个操作失败,实际上还是可以重试的。如果最终还是重试失败,那么就要走到第4步中的回滚分支。实现上,回滚也是可以重试的,并且回滚如果中断了,理论上还可继续重试第3步,这时记录已经完成或者回滚完成的操作也是必要的了。
这不和之前一样一样嘛,显然事情没有这么简单。这已经走到分布式事务的领域了,简单罗列一下关键点:
最后,回到事务,即ACID,简单总结如下:
标签:
原文地址:http://www.cnblogs.com/nofree/p/4271594.html