持久性和一致性:事务日志的记录以及在记录数据时和数据保持一致

持久性和一致性:事务日志的记录以及在记录数据时和数据保持一致。

事务日志的概念

数据读写这个写入过程和数据最终持久写入到磁盘是两个概念,

看下系统调用函数write

以下引用来自:https://linux.die.net/man/2/write

写道
A successful return from write() does not make any guarantee that data has been committed to disk. In fact, on some buggy implementations, it does not even guarantee that space has successfully been reserved for the data.

更何况我们在读写操作数据时也并不是每次都直接将数据往磁盘中写。

毕竟每次读写磁盘都是有代价的,每次往磁盘写数据,也不管是有多少数据要写入,这其中的IO代价很大。

根据上面的引用,数据的写入操作并不代表数据就成功写入到磁盘中,它并不保证数据写入请求提交到磁盘,并最终持久写入的磁盘。

执行写入操作,数据并没有真正写入到磁盘中,系统会有一块缓存(disk cache)用来缓存写入的数据,写入的数据可能还存在缓存中,这个disk cache叫modified buffer cache,这时候的数据是modified in-core data,。当然,针对一些老版本的Linux内核,可以通过hdparm(8) or sdparm(8)来禁用这个缓存(disk cache)功能。

缓存buffer:

struct sysfs_buffer {
	size_t			count;
	loff_t			pos;
	char			* page;
	struct sysfs_ops	* ops;
};

写入write操作的系统调用:

static ssize_t
sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	struct sysfs_buffer * buffer = file->private_data;

	count = fill_write_buffer(buffer,buf,count);
	if (count > 0)
		count = flush_write_buffer(file,buffer,count);
	if (count > 0)
		*ppos += count;
	return count;
}

我们都知道,在执行IO操作的时候,写入一条数据,比如写入文件,在关闭之间,我们会执行一个类似flush的刷盘操作。这个操作对应的系统调用fsync,fdatasync。

flush操作:

static int 
flush_write_buffer(struct file * file, struct sysfs_buffer * buffer, size_t count)
{
	struct attribute * attr = file->f_dentry->d_fsdata;
	struct kobject * kobj = file->f_dentry->d_parent->d_fsdata;
	struct sysfs_ops * ops = buffer->ops;

	return ops->store(kobj,attr,buffer->page,count);
}

对于磁盘来说,IO数据操作就是一个个操作事务,最后的类似flush就相当于commit,为了保证数据能写入到磁盘,我们一般最好执行下这个flush操作,虽然这个并非必要,没有这个flush,数据一般也会写入到磁盘,但是前面也说了,写入操作也没保证数据会写入磁盘,更何况可能会发生一些复杂意外的情况。flush操作会等待磁盘报告写入的数据是否传输完(到磁盘,也就是真正写进磁盘)。

|--用户程序--|---------------------------os---------------------------|--disk--|

                                             (commit)

                                    (does not guarantee)

    write ----> write buffer(cache)  --->  transfer request  ---> disk

    write ----> write buffer(cache)  ---> 

                                          flush  --->  transfer request  ---> disk

                                             |                                                  |

                                              <--------------report----------------

执行这个操作才会使数据真正持久写入到磁盘当中。假设我们将数据值称为数据的状态,当前的值表示数据当前所处的状态,更新数据后,数据有了新的值,表示一个新的状态,这个更新操作使得数据从一个状态变更到另一个状态。

在数据写入到磁盘后,数据的值状态得到持久化盘,操作结束。已经持久化落盘的数据状态是不能再发生变化的,也不能再回到更新之前的状态。除非再次重新执行一个数据写入操作,这个操作执行的是之前操作的逆操作,但这已经是一次新的操作了。

简单的一个写入操作,比如向文件中写入一条简单的数据“hello,world!”。

。。。。

假设我们现在写两条数据,分别写入到两个文件,为了保证数据能持久化盘,在写入数据后都执行一下flush操作。这里就有个问题,这两次flush操作能保证都flush成功吗?如果第一次flush成功,后面一次flush失败了呢?

这里还只是一个简单的问题,如果两次flush操作都成功了那就万事大吉了,就算两次flush失败了也问题不大,要么重新都flush一下,要么就认为这次的操作失败了也没什么。如果是一次是成功的,另一次是失败了,我们可以把失败的那次重新flush下,如果没有什么大问题,重新flush也许就成功了。

只是。。。

如果第二次flush时,或者有失败时reflush的时候,磁盘损坏了,导致flush无法将数据持久化到盘了怎么办?或者是因为其他什么原因,比如程序挂了,系统故障的原因,还有可能。。。掉电了!!!链路被切断了!!!网线被拔了!!!(写个文件怎么也会经过网络链路,还扯上网线?还是有这种情况的。)

这就麻烦了,严重的时候时候可能连恢复的可能都没有了。。。

这里说的并不是简单的向文件中写入比如像“hello,world!”这样简单的看似毫无用处的一条数据。

这里还只是从数据操作层面来讨论这个问题。

假设我们在更新数据的同时,也同时记录一份日志来记录每次数据更新的记录变更。

事务日志应该和实际数据(状态)变化一致。包括已磁化落盘的数据实际的历史状态的变化,以及还未来得及持久落盘的数据将要执行的状态变更。