ITPub博客

首页 > 云计算 > 开源云工具 > etcd-raft-存储分析

etcd-raft-存储分析

原创 开源云工具 作者:java06051515 时间:2020-03-18 17:09:08 0 删除 编辑

etcd raft介绍

etcd raft是目前使用最广泛的raft库,如果想深入了解raft请直接阅读论文 “In Search of an Understandable Consensus Algorithm”( raft.github.io/raft.pdf ), etcd raft在etcd, Kubernetes, Docker Swarm, Cloud Foundry Diego, CockroachDB, TiDB, Project Calico, Flannel等分布式系统中都有应用,在生成环境得到了验证。 传统raft库的实现都是单体设计(集成了存储层、消息序列化、网络层等), etcd raft继承了简约的设计理念,只实现了最核心的raft算法, 这样更加的灵活。etcd将网络、日志存储、快照等功能分开,通过独立的模块实现,用户可以在需要时调用。etcd自身实现了自己的一套raft配套库:etcd-wal(用于存储日志),snap(用于存储快照),MemoryStorage(用于存储当前日志、快照、状态等信息以供raft核心程序使用)。

etcd wal介绍

WAL是write ahead log的缩写,etcd使用wal模块来完成raft日志的持久化存储,etcd对wal的所有实现都放在wal目录中。

wal数据结构

type WAL struct {
lg *zap.Logger

dir string  // the living directory of the underlay files

// dirFile is a fd for the wal directory for syncing on Rename
dirFile *os.File

metadata []byte  // metadata recorded at the head of each WAL
state raftpb.HardState  // hardstate recorded at the head of WAL

start walpb.Snapshot  // snapshot to start reading
decoder *decoder  // decoder to decode records
readClose func() error  // closer for decode reader

mu sync.Mutex
enti uint64  // index of the last entry saved to the wal
encoder *encoder  // encoder to encode records

locks []*fileutil.LockedFile  // the locked files the WAL holds (the name is increasing)
fp *filePipeline
}

上述为wal的数据结构,通过用wal.go文件中的Create()方法来获取wal的实例。wal首先会创建一个临时目录并初始化相关变量,并创建和初始化第一个wal文件,等所有的操作都初始化完成后直接更改临时目录的名字完成wal实例的初始化。

文件组织

wal的所有日志放在一个指定目录下,日志的文件名以 .wal 作为结尾,格式为-.wal,seq和index的格式都为%016x,例如:0000000000000001-0000000000000001.wal。index代表这个文件中第一条raft日志的index,seq是这个文件的序列号(依次递增)。

每个文件的大小默认为64M,当文件大于64M时,wal会自动生成新的日志文件用于存储日志。每个日志文件都会使用flock锁定文件,参数为LOCK_EX,这是一把独有锁,同一时间只能有一个进程可以操作这个日志文件,所以当wal占有这个文件时,通过进程是无法删除这个文件的。

日志逻辑组织

wal日志可以存储多种类型的数据,具体如下。



  • crcType 每个新的日志文件的第一条记录都会是crcType类型的记录,crcType也只会在每个日志文件的开始时写入,用于记录上一个文件最后的crc是多少
  • metadataType 每个新的日志文件中metadataType紧跟在crcType记录后面,每个日志文件只会出现一次
  • stateType 这种日志类型会在两种情况下加入:
  1. 自动切分日志文件时,新的日志文件中,紧跟在metadataType后面会存入一条stateType的日志
  2. 当raft核心程序ready中返回hard state时也会存储该类型的日志
  • snapshotType wal日志中只会存储snapshot的term和index,具体的数据存储在专门的snapshot中,每次存储快照都会在wal日志中存储一个wal的快照。当存储快照时,会将快照之前index的日志文件都释放掉。wal中存储的snapshot主要用于检查快照是否正确。
  • 日志读写

    wal通过封装的encoder和decoder模块来实现日志读写。

    写日志

    encoder模块把会增量的计算crc和数据一起写入到wal文件中。 下面为encoder数据结构

    type encoder struct {
    mu sync.Mutex
    bw *ioutil.PageWriter

    crc hash.Hash32
    buf []byte  //缓存空间,默认为1M,降低数据分配的压力
    uint64buf []byte
    }

    wal通过encoder实现写日志,在这个模块中会完成crc的计算。wal为了更好的管理数据,日志中的每条数据都会以8字节对齐(wal会自动对齐字节)。 日志写入的流程如下。


    图中的crc是增量计算,以之前的所有日志数据为增量基础。 wal只关注写入日志,不会校验日志的index是否重复,但是如果重启这个Node的话,系统会自动过滤掉中间混杂的日志。

    日志切分

    wal实现了日志自动切分,当日志数据大于默认的64M时就会生成新的文件写入日志,日志的切分通过wal.go文件中的cut方法来实现。cut方法只会在调用wal中的Save方法才会触发调用,新文件的第一条记录就是上一个wal文件最后的crc。

    日志compact

    wal没有实现日志的自动compact,系统只提供了MemoryStorage的日志compact方法(需要用户主动调用)。

    file_pipeline模块

    wal新建新的文件时都是先新建一个tmp文件,当所有操作都完成后再重命名这个文件。wal使用file_pipeline这个模块在后台启动一个协程时刻准备一个临时文件以供使用,从而避免临时创建文件的开销。

    etcd snap介绍

    etcd raft自带了 go.etcd.io/etcd/etcdserver/api/snap模块来实现快照的存储。

    文件组织

    在snap模块中一个快照用一个后缀名为.snap的文件存储,文件格式为-.snap, term和index分别代表快照日志所处的term和index。 每个快照具体存储结构如下图:


    详细介绍

    系统可以有多个快照,snap模块使用Snapshotter结构统一管理快照。

    type Snapshotter struct {
    lg *zap.Logger
    dir string
    }

    上面snapshotter的结构代码,snapshotter主要用于存储和读取快照。

    快照具体存储的内容需要用户来指定,例如在raft的官方例子中直接将当时的kv数据Marshal之后存储到快照中。

    func (s *kvstore) getSnapshot() ([]byte, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    return  json.Marshal(s.kvStore)
    }

    何时打快照

    在etcd-raft中用户可以选择何时打快照,在etcd的官方案例中打快照的方法是maybeTriggerSnapshot(),这个方法在节点的Ready()方法返回时调用,当前提交的index值与上一次大快照的index值大于10000时会打新的快照。

    etcd MemoryStorage介绍

    MemoryStorage用于存储raft节点临时的数据,包括entrys、快照等。用户将数据存储到memoryStorage中,raft节点也会使用这些数据。包括entrys的传递、快照的发送等都是从memoryStorage中发送。

    // MemoryStorage implements the Storage interface backed by an
    // in-memory array.
    type MemoryStorage struct {
    // Protects access to all fields. Most methods of MemoryStorage are
    // run on the raft goroutine, but Append() is run on an application
    // goroutine.
    sync.Mutex

    hardState pb.HardState
    snapshot pb.Snapshot
    // ents[i] has raft log position i+snapshot.Metadata.Index
    ents []pb.Entry
    }

    memoryStorage会存储最新的entrys(包括哪些没有commit)、快照和状态,用户在收到其它节点发送的相关数据时需要将数据存储到memorystorage中。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31559758/viewspace-2681225/,如需转载,请注明出处,否则将追究法律责任。

全部评论

注册时间:2018-10-26

  • 博文量
    148
  • 访问量
    115429