WAL(即write ahead log,也被叫做journaling,cephfs的mds采用类似的机制)是对很多系统(特别是关系型数据库)ACID中的A和D的保障。日志内容会优先持久化到底层磁盘,保证在断电之类的情况出现时,也能读取日志对系统数据进行恢复。
WAL记录的是对数据的修改操作。日志采用追加写的方式维护。
WAL也是系统做redo和undo操作的基础。系统正/反向执行日志即可实现。
日志的操作(大多数代码都是按照这些功能进行组织)
- 创建日志
- 打开日志
- 保存日志(追加将日志写入),查找到当前日志的末尾,如果日志过大创建新的日志文件。
- 日志回放
- 日志裁剪(回收)
但是日志的重放存在问题,如日志记录的信息一致没有合理的移除(如cephfs调整相应的journal参数,让所有日志都不失效。),导致重放的过程会非常耗时。除此之外过多的日志在一定程度上占有存储空间。
etcd的方式
日志文件大小默认为64MB,采用预分配提高效率。etcd将日志以json的格式持久化到磁盘中。etcd中master会向slave复制journal信息。slave执行重放操作,让数据副本之间保持一致,这两个过程即推送日志和Follower执行追加写。
etcd克服日志过长的方案采用快照+日志(etcd采用的方式,每1W条记录执行一次快照),即数据定期快照,节点恢复时只需要将快照内容之后的日志进行重放即可恢复数据。
etcd每1W执行一次快照,方式将内存中的数据库复制一份,转成json格式。快照的版本号等信息存入一起添加的元数据信息中。slave节点在重启时,如果master复制过来的日志已经回收,则采用快照(反映最新的数据库状态,回收发生在快照之后)将节点之间的数据恢复到一致的状态。
日志回收则是将快照前的日志回收。
redis的方式
redis除了本身的RBD进行数据持久化,也提供了AOF(append only file)持久化功能。打开该功能后,服务器执行完写操作之后,将同样的命令将以一定的格式写入到缓存区aof_buf中,并由周期执行线程flushAppendOnlyFile写入到文件中。系统通过读取所保存的这些写操作进行恢复。(redis恢复过程需要创建一个伪客户端,redis命令执行需要redis上下文。)
redis提供AOF文件重写的方式将冗余的日志删除(类似于合并操作)。redis采用单线程处理请求,因此,重写会占用处理能力。redis采用的后台重写方式:利用子进程而不是线程(避免锁的竞争)执行。一份数据将会产生追加写入AOF缓冲区和AOF重写缓冲区的两个命令, 子进程将AOF缓冲区内容写入到AOF文件中。如果此时有新的记录产生则会被记录到重写缓冲区中。当子进程执行退出执行,通过信号通信,父进程调用相应的处理函数,把重写缓冲区中的内容写入到AOF文件中,并原子地把就文件覆盖,从而完成AOF的更新。
这样做大大提升里执行效率,只有信号处理阶段会对父进程进行阻塞操作。