etcd之日志和快照管理

系列文章目录

浅谈分布式系统与一致性协议(一)
浅谈分布式系统与一致性协议(二)
浅谈分布式系统与一致性协议(三)
深入浅出之etcd
深入浅出之etcd(二)
etcd版本之v3
etcd之安全性阐述
etcd的多版本并发控制


目录

  • 系列文章目录
  • 概述
  • 数据的持久化和复制
  • etcd的日志管理
    • WAL文件物理格式
    • WAL文件的初始化
    • WAL追加日志项
  • etcd v2的快照管理
    • 创建快照

概述

etcd对数据的持久化采用的是binlog(日志,也称为WAL,即Write-Ahead-Log)加Snapshot(快照)的方式

在计算机科学中,预写式日志(Write-Ahead-Log,WAL)是关系数据库系统中用于提供原子性和持久性(ACID中的两个特性)的一系列技术。在使用WAL系统中,所有修改在提交之前都要写入log文件中

log文件中通常包括redo信息和undo信息。假设一个程序在执行某些操作过程中机器掉电了。在重新启动时,程序可能需要直到当时执行得操作是完全成功了还是部分成功或者完全失败。如果使用了WAL,那么程序就可以检查log文件,并对突然掉电时计划执行的操作内容与实际上执行的操作内容进行比较。在这个比较的基础上,程序就可以决定是撤销已做的还是继续完成已做的操作,或者保持原样

WAL允许用in-space的方式更新数据库。另一种用来实现原子更新的方法是shadow paging,它并不是一种in-place方式。用in-place方式进行更新的主要有点是减少索引和块列表的修改。ARIES是WAL系列技术常用的算法。在文件系统中,WAL通常称为journaling。PostgreSQL也是用WAL来提供oint-in-time恢复和数据库复制特性的

etcd数据库的所有更新操作都需要先写入到binlog中,而binlog是实时写到磁盘上的,因此这样就可以保证不会丢失数据,即使机器断电,重新启动以后etcd也能通过读取并重放binlog里面的操作记录来重新建立数据库

etcd数据的高可用性和一致性是通过Raft算法实现的,Master节点会通过Raft协议向Slave节点复制binlog,Slave节点根据binlog对操作进行重放,以维持数据的多个副本的一致性。也就是说binlog不仅仅是实现数据库持久化的一种手段,其实还是实现不同副本间一致性协议的重要手段。客户端对数据库发起所有写操作都会记录在binlog中,待主节点将更新日志在集群多数节点之间完成同步后,以便内存中的数据库中应用该日志项的内容,进而完成一次客户端的写请求

数据的持久化和复制

先看个例子。例如,通过以下命令向etcd中插入一个键值对

etcdctl set /foo bar

etcd会在默认的工作目录下生成两个子目录:snap和wal。两个目录的作用说明如下:

  • snap:用于存放快照数据。etcd为了防止WAL文件过多就会创建快照,snap用于存储etcd的快照数据状态
  • wal:用于存放预写式日志,其最大的作用是记录整个数据变化的全部历程。在etcd中,所有数据的修改在提交之前,都要写入WAL中。使用WAL进行数据的存储使得etcd拥有故障快速恢复和数据回滚两个重要的功能

故障快速恢复:如果你的数据遭到颇快,就可以通过执行所有WAL中记录的修改操作,快速从原始的数据恢复到数据损坏之前的状态

数据回滚(undo)/重做(redo):因为所有的修改操作都被记录在WAL中,所以进行回滚或者重做时,只需要反响或者正向执行日志即可

etcd的日志管理

etcd提供了一个WAL日志库,日志追加等功能均有该库完成。下面让我们先看一下WAL数据结构定义

WAL数据结构

WAL数据结构定义如下:

type WAL struct{
	dir string
	dirFile *os.File
	metadata []byte
	state raftpb.HardState
	start walpb.Snapshot
	decoder *decoder
	readClose func() error
	mu sync.Mutex
	enti uint64
	encoder *encoder
	locks []*fileutil.LockedFile
	fp *filePipeline
}

WAL管理所有的更新日志,主要处理日志的追加,日志文件的切换,日志的回放等操作

WAL文件物理格式

etcd所有的日志项最终都会被追加存储到WAL文件中,日志项有很多类型,具体如下:

  • metadataType :这是一个特殊的日志项,被写在每个WAL文件的头部
  • entryType:应用的更新数据,也是日志中存储的最关键数据
  • stateType:代表日志项中存储的内容时快照
  • crcType:前一个WAL文件里面的数据的crc,也是WAL文件的第一个记录项 snapshotType:当前快照的索引{term,index},即当前快照位于哪个日志记录,不同于stateType,这里只是记录快照的索引,而非快照的数据。

每个日志项都由四部分组成:

  • type:日志项类
  • crc:校验和
  • data:根据日志项类型存储的实际数据也不尽相同,如果snapshotType类型的日志项存储的是快照的日志索引,crcType类型的日志项中则无数据项,其crc字段便充当了数据项
  • padding:为了保持数据项8子节对其而填充的数据

WAL文件的初始化

etcd的WAL库提供了初始化方法,应用需要显示调用初始化方法来完成日志初始化的功能,初始化方法主要包括两个函数Create()与Open()

Create()所做的事情比较简单,具体如下:

  1. 创建WAL目录,用于存储WAL日志文件
  2. 预分配第一个WAL日志文件,默认是64MB,使用预分配机制可以提高写入性能
  3. Open则是在Create完成后被调用,主要是打开WAL目录下的日志文件
  4. Open的主要作用是找到当前快照以后的所有WAL日志,这是因为快照之前的日志我们不再关心,因为日志的内容肯定都已经被更新至快照了,这些日志也是在后面回收日志操作中可以被删除的部分

WAL追加日志项

日志项的追加通过调用etcd的wal库的Save()方法来实现,该函数的核心内容具体如下:

  • 调用saveEntry()将日志项存储到WAL文件中
  • 如果追加后日志文件超过了既定的SegmentSizeBytes大小,则需要调用w.cut()进行WAL文件的切换,即关闭当前WAL日志,创建新的WAL日志,继续用于日志追加
  • cut()的目的用于实现WAL文件切换的功能,每个WAL文件的预设大小均为64MB,一旦超过该大小,便会创建新的WAL文件,这样做的好处便是对旧的WAL文件进行删除

etcd v2的快照管理

etcd v2是一个纯内存数据库,写操作先通过Raft协议复制binlog,复制成功后将数据写入到内存中,整个数据库在内存中是一个简单的树结构,其轻微将数据实时写入到磁盘中,持久化考的是binlog和定期做快照实现的,总的俩讲,etcd v2做快照的方法就是将内存中的整个数据库复制一份,然后序列化成JSON,写入到磁盘中,称为快照。做快照的时候使用的是复制出来的数据库,客户端的读写请求依然会落到原始的数据库,也就是说做快照的操作不会阻塞客户端的读写请求

因为操作系统对内存进行了分页,同时内存的复制操作实际是COW的,所以只有当复制的某一个内存页发生更改时才会发生复制行为,即只有那些被客户端读到的数据也才会在内存中被复制,那些没有读到的压根不会发生复制。

快照数据结构如下:

type ConfState struct{
	Nodes []uint64
}

type SnapshotMetadata struct{
	ConsState ConfState
	Index uint64
	Term uint64
}
type Snapshot struct{
	Data []byte
	Metadata SnapshotMetadata
}

创建快照

创建快照的时机时在请求的处理的流程之中,具体来说,Raft协议每获取到日志项之后,在处理该日志的过程中就会判断是否创建快照。创建快照的具体包含如下几个步骤:

  1. 判断是否传创建快照,该过程有一定的代价,因此不会每次都执行。
  2. 创建快照,由应用实现具体的创建方法
  3. 通过raftStorage创建快照
  4. 存储日志
  5. 进行日志回收(compact)

对于执行快照创建的时机进行判断时,etcd采用较为简单的策略:每处理10000条日志进行一此快照

创建快照的的操作代码如下,直接将内存中的数据库复制一份转换成JSON即可

func (s *kvstore)getSnapshot()([]byte,error){
	s.mu.Lock()
	defers.mu.Unlock()
	return json.Marshal(s.kvStore)
}

拿到整个数据库快照之后,还要添加一些metadata,比如该快照的版本号等,然后就可以将快照进行持久化了

func (rc *raftNode)saveSnap(snap raftpb.Snapshot) error{
	walSnap:=walpb.Snapshot{
		Index:=snap.Metadata.Index,
		Term:=snap.Metadata.Term,
	}
	if erro:=rc.wal.SaveSnapshot(WalSnap);err!=nil{
		return err
	}
	if err:=rc.snapshotter.SaveSnap(snap);err!=nil{
		return err
	}
	return rc.wal.ReleaseLockTo(snap.Metadata.Index)
}

持久化存储具体包括以下几个内容

  • 快照索引:即当前快照的其实日志项索引信息(term/index),该信息被存储在WAL日志文件所在目录
  • 快照数据:即快照的真正数据

你可能感兴趣的:(一起学云原生,数据库,golang,etcd,云原生)