[游戏或软件] 讨论一个开发、硬件相关的问题,关于存储设备和事务原子性的实现
AreYouOK?
2008-12-27
打游戏累了的没事来看看吧。
最近在和文件打交道,对文件进行操作的时候需要简单的实现事务的原子性和一致性。 我参考了一下SQLite的原子提交原理: http://chensheng.net/p/sqlite/auto_commit_zh_cn.html 为了实现功能,首选它对存储设备做了若干假设,其中有一条如下: 引用 SQLite并不假定扇区写操作是原子的。然而,我们假定扇区写操作是线性的。所谓“线性”是指,当开始扇区写操作时,硬件从前一个扇区的结束点开始,然后一字节一字节的写入,直到此扇区的结束点。这个写操作可能是从尾向头写,也可能是从头向尾写。如果在一个扇区写入操作时发生掉电故障,这个扇区可能会一部分已经修改完成,还有一部分还没来得及进行修改。SQLite的关键设定是这样的:如果一个扇区的任何部分发生修改,那么不是它开始的部分发了变化,就是它结束部分发生了变化。所以硬件从来都不会从一个扇区的中间部分开始写入。我们不知道这个假定是否总是真实的,但无论如何,看起来还是蛮合理的。
这一点让人非常不爽。因为如果假定扇区写是原子操作,性能能够得到提高,并且编程的复杂性也会降低。 例如,如果一个事务只修改了一个扇区的内容,那么可以不写日志,直接写入数据文件。 通常来说,日志数据写入完成以后,需要更新一个设置一个标志位,表示日志已经成功写入,接下来将要写入数据文件。这个时候,往往还要写入一些额外的附加信息,那么这几个字段应该在一个扇区里,同时写入,如果扇区是原子写的话,会非常简单,否则要做容错。 如果按照SQLite的假定,那么我猜它是这样判定扇区是否完全写入的(这样太麻烦了):假设扇区是512,那么记录状态的那个扇区的第一个字节和最后一个字节是标志位,每次都更新这两个字节,如果过程中发生了掉电故障,那么重启回滚的时候只要看看第一个字节和最后一个字节是否更新为相同的值,就知道整个扇区是否写入成功。 我写的程序对事物的要求没有那么严格,允许丢失最后写入的数据,但是不允许文件被损坏。 这里是第一个要讨论的问题,现在到底能不能假设扇区原子写,现代磁盘是否够应该具备这个功能? 即使是串口硬盘,传输的时候一个一个个传过去,但是最后能够按整扇区原子写入? 对于将要普及的固态硬盘,我询问了一个从事相关行业的同学,得到答复如下: 引用 现在的固态硬盘肯定都是NAND的。NAND肯定是一个page一起写入和读出的,不存在一半数据写进去的情况,因为在物理上,它是所以晶体管的门同时打开,让数据进去的。一个page一半是1K个word,就是2K个byte。
第二个问题是: 现代硬盘的扇区是512,还是4k的已经普及了,对于SCSI硬盘又如何呢? 事务提交完成之前,需要保证数据确实写到物理设备上去了。在java api中只有3个方法,OutputStream上的flush方法,只保证返回时已将数据传递给操作系统,不保证写入物理设备。MappedByteBuffer和FileChannel的force方法,保证当存储设备在本地时,方法返回时,数据已经写入了,但是: 引用 If the file does not reside on a local device then no such guarantee is made.
那么第三个问题: 非local device包括些什么,是仅仅指网络文件系统,例如windows下用\\ip\shareDir共享的文件夹,还是连盘阵也算?如果连盘阵也算的话那么就小有麻烦了。另外lock在盘阵上的表现也是值得测试的。 |
|
icewubin
2008-12-27
1.我不是这方面的专家,我的观点是要看操作系统的吧。
2.你要么再借鉴一下SVN的文件读写策略或者是技术? |
|
AreYouOK?
2008-12-27
icewubin 写道 1.我不是这方面的专家,我的观点是要看操作系统的吧。
2.你要么再借鉴一下SVN的文件读写策略或者是技术? 因为操作系统,主要是文件系统的差异(例如它们会对写入的数据做缓存),所以SQLite做出了一些悲观的假定。很多假定是绕过操作系统的,例如关于扇区写的假定。如果硬盘的扇区是原子写,不管在什么操作系统上,我都可以确定至少写512个字节是个原子操作,不至于出现要写一个4字节的int,结果写入了前面两个字节,而后面两个字节没写进去的情况。 实际上这里隐含了一个假定,即文件系统会按照扇区来存放文件,文件的第一个字节一定会放在扇区的第一个字节,因此我们就知道从0~511字节都是第一扇区的(假设扇区是512)。这个假定应该没有什么问题,因为文件系统本身也要实现事务,这样存放绝对是最方便的。格式化磁盘的的时候,分配单元的大小确实也是扇区的整数倍。 2、SVN有两种存储方式,一种使用BDB数据库,另外一种使用文件。后者每次提交的版本,在后台都存为一个文件。这个其实是很好实现的,首先它把提交来的数据写在临时文件里面,提交完毕以后把临时文件move到数据目录就可以了。 文件系统会保证单个文件的移动、覆盖、重命名、删除是原子操作,例如不可能出现一个文件被删除了一半的情况。SQLite也有这样的假定。 |
|
icewubin
2008-12-27
那BDB数据库的实现方式呢?
你说的SVN第二种包括FSFS么? |
|
AreYouOK?
2008-12-28
icewubin 写道 那BDB数据库的实现方式呢?
你说的SVN第二种包括FSFS么? BDB=Berkeley DB,本身就是一个数据库,已经被oracle收下,底层是一个数据库就不存在这个问题了。 我说的第二种方式就是FSFS。一个事务就是一个文件,不同的事物是不同的文件。文件系统保证移动、重命名、删除是原子操作,这样事务的原子性和一致性就很好实现。先写在tmp目录里面,提交完毕后,移动到数据目录里面。如果提交过程中失败,把临时文件扔掉就可以了。 对于一个100M的文件,需要修改其中的30K,这样的情况,实现事务就复杂点。需要写日志,提交正式数据的过程中出了毛病,是需要回滚的。 apache有个Commons Transaction能实现文件事务,不过它用的是文件系统的功能来实现事务,不支持随机写。对于上面说到的100M的文件,修改30K的情况,它需要把这个文件整个重写一遍(写在临时目录中,然后覆盖),因此稍微有点性能要求,就不能采用的。 |
|
icewubin
2008-12-28
太专业了,应该发在和驱动编写相关的板块吧。
|
|
jiangshaolin
2008-12-28
楼主研究精神让人佩服,深入...
|