Etcd (三) WAL

WAL (Write ahead logging) : 存放预写式日志,最大的作用是记录了整个数据变化的全部历程。在 etcd 中,所有数据的修改在提交前,都要先写入到WAL中。.wal文件命名格式为seq+idx

WAL 机制使得 etcd 具备了以下两个功能:

  • 故障快速恢复: 当你的数据遭到破坏时,就可以通过执行所有 WAL 中记录的修改操作,快速从最原始的数据恢复到数据损坏前的状态。

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

主要方法

Create

创建临时的WAL文件名和目录,对当前文件上锁并预分配空间,然后将临时文件重命名,作为原子操作

Save

核心功能,对小于预分配64MB的文件直接持久化日志和state;大于64MB的部分进行cut,分配(seq+1,index+1)的新名字,以此打开filepipeline预分配的文件进行下一步操作

Open

在指定index打开文件,读取该index后所有的日志信息

ReadAll

读取解码后的Record,主要包含以下几种类型:

  • entryType: raft日志,占最多
  • stateType: 存储term,vote,commit相关的状态信息
  • metadataType: 元数据,WAL中需要保持一致
  • crcType: 用于检验文件完整性
  • snapshotType: 快照的index和term信息

Verify

用于检验文件是否被污染。每次cut64MB后都会更新crc,记录到文件中,从而能够核验其正确性

WAL 定义

// WAL is a logical representation of the stable storage.
// WAL is either in read mode or append mode but not both.
// A newly created WAL is in append mode, and ready for appending records.
// A just opened WAL is in read mode, and ready for reading records.
// The WAL will be ready for appending after reading out all the previous records.
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

    unsafeNoSync bool // if set, do not fsync

    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 创建

// Create creates a WAL ready for appending records. The given metadata is
// recorded at the head of each WAL file, and can be retrieved with ReadAll
// after the file is Open.
func Create(lg *zap.Logger, dirpath string, metadata []byte) (*WAL, error) {
    if Exist(dirpath) {
        return nil, os.ErrExist
    }

    if lg == nil {
        lg = zap.NewNop()
    }

    // keep temporary wal directory so WAL initialization appears atomic
    // 先在.tmp上修改,修改完后改名,从而保证原子性
    tmpdirpath := filepath.Clean(dirpath) + ".tmp"
    if fileutil.Exist(tmpdirpath) {
        if err := os.RemoveAll(tmpdirpath); err != nil {
            return nil, err
        }
    }
    defer os.RemoveAll(tmpdirpath)

    if err := fileutil.CreateDirAll(lg, tmpdirpath); err != nil {
        lg.Warn(
            "failed to create a temporary WAL directory",
            zap.String("tmp-dir-path", tmpdirpath),
            zap.String("dir-path", dirpath),
            zap.Error(err),
        )
        return nil, err
    }

    // path: dir/walname
    // walname: seq+index
    p := filepath.Join(tmpdirpath, walName(0, 0))

    // 对当前文件上锁
    f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, fileutil.PrivateFileMode)
    if err != nil {
        lg.Warn(
            "failed to flock an initial WAL file",
            zap.String("path", p),
            zap.Error(err),
        )
        return nil, err
    }

    // 找到文件末尾
    if _, err = f.Seek(0, io.SeekEnd); err != nil {
        lg.Warn(
            "failed to seek an initial WAL file",
            zap.String("path", p),
            zap.Error(err),
        )
        return nil, err
    }

    // 预分配64MB
    if err = fileutil.Preallocate(f.File, SegmentSizeBytes, true); err != nil {
        lg.Warn(
            "failed to preallocate an initial WAL file",
            zap.String("path", p),
            zap.Int64("segment-bytes", SegmentSizeBytes),
            zap.Error(err),
        )
        return nil, err
    }

    // 新建WAL,加上encoder并保存snapshot
    w := &WAL{
        lg:       lg,
        dir:      dirpath,
        metadata: metadata,
    }
    w.encoder, err = newFileEncoder(f.File, 0)
    if err != nil {
        return nil, err
    }
    // 将当前上锁的文件加入到locks数组中(存放已经上锁的文件)
    w.locks = append(w.locks, f)
    if err = w.saveCrc(0); err != nil {
        return nil, err
    }
    if err = w.encoder.encode(&walpb.Record{Type: metadataType, Data: metadata}); err != nil {
        return nil, err
    }
    if err = w.SaveSnapshot(walpb.Snapshot{}); err != nil {
        return nil, err
    }

    // 将.tmp改名重命名,原子操作
    logDirPath := w.dir
    if w, err = w.renameWAL(tmpdirpath); err != nil {
        lg.Warn(
            "failed to rename the temporary WAL directory",
            zap.String("tmp-dir-path", tmpdirpath),
            zap.String("dir-path", logDirPath),
            zap.Error(err),
        )
        return nil, err
    }

    var perr error
    defer func() {
        if perr != nil {
            w.cleanupWAL(lg)
        }
    }()

    // directory was renamed; sync parent dir to persist rename
    pdir, perr := fileutil.OpenDir(filepath.Dir(w.dir))
    if perr != nil {
        lg.Warn(
            "failed to open the parent data directory",
            zap.String("parent-dir-path", filepath.Dir(w.dir)),
            zap.String("dir-path", w.dir),
            zap.Error(perr),
        )
        return nil, perr
    }
    dirCloser := func() error {
        if perr = pdir.Close(); perr != nil {
            lg.Warn(
                "failed to close the parent data directory file",
                zap.String("parent-dir-path", filepath.Dir(w.dir)),
                zap.String("dir-path", w.dir),
                zap.Error(perr),
            )
            return perr
        }
        return nil
    }
    start := time.Now()

    // 将上述操作同步
    if perr = fileutil.Fsync(pdir); perr != nil {
        dirCloser()
        lg.Warn(
            "failed to fsync the parent data directory file",
            zap.String("parent-dir-path", filepath.Dir(w.dir)),
            zap.String("dir-path", w.dir),
            zap.Error(perr),
        )
        return nil, perr
    }
    walFsyncSec.Observe(time.Since(start).Seconds())
    if err = dirCloser(); err != nil {
        return nil, err
    }

    return w, nil
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容