当前位置:网站首页>ETCD数据库源码分析——etcdserver bootstrap从快照中恢复store
ETCD数据库源码分析——etcdserver bootstrap从快照中恢复store
2022-07-17 09:03:00 【肥叔菌】
recoverSnapshot函数func recoverSnapshot(cfg config.ServerConfig, st v2store.Store, be backend.Backend, beExist bool, beHooks *serverstorage.BackendHooks, ci cindex.ConsistentIndexer, ss *snap.Snapshotter) (*raftpb.Snapshot, backend.Backend, error)
用于从快照数据总恢复v2 store和v3 store。因此我们可以看到其需要的形参:
- recoverSnapshot函数需要将v2存储st和v3存储be传入函数,以及snapshotter ss用于获取磁盘上的快照文件。
beExist := fileutil.Exist(cfg.BackendPath())
recoverSnapshot函数需要的beExist是检测BoltDB数据库文件是否存在,true表示存在。ci := cindex.NewConsistentIndex(nil)
和ci.SetBackend(be)
cindex provides an interface and implementation for getting/saving consistentIndexbeHooks := serverstorage.NewBackendHooks(cfg.Logger, ci)
be := serverstorage.OpenBackend(cfg, beHooks)
在该函数中会启动一个后台协程完成Backend实例的初始化
recoverSnapshot函数首先调用ValidSnapshotEntries函数返回给定目录中wal日志中的所有有效快照条目(snapshotType的wal日志条目),如果快照条目的raft log index小于或等于最近提交的硬状态的raft log index,则快照条目有效;调用LoadNewestAvailable函数获取最新的快照数据。LoadNewestAvailable函数的流程就是通过loadMatching函数对快照数据中包含的元数据和snapshotType的wal日志条目进行比较匹配,获取最新且正确的快照数据。
func recoverSnapshot(cfg config.ServerConfig, st v2store.Store, be backend.Backend, beExist bool, beHooks *serverstorage.BackendHooks, ci cindex.ConsistentIndexer, ss *snap.Snapshotter) (*raftpb.Snapshot, backend.Backend, error) {
// Find a snapshot to start/restart a raft node ValidSnapshotEntries返回给定目录中wal日志中的所有有效快照条目。如果快照条目的raft log index小于或等于最近提交的硬状态的raft log index,则快照条目有效。
walSnaps, err := wal.ValidSnapshotEntries(cfg.Logger, cfg.WALDir())
if err != nil {
return nil, be, err }
// snapshot files can be orphaned if etcd crashes after writing them but before writing the corresponding bwal log entries 如果etcd在写入快照文件后但在写入相应的bwal日志条目之前崩溃,则快照文件可以孤立orphaned
snapshot, err := ss.LoadNewestAvailable(walSnaps)
if err != nil && err != snap.ErrNoSnapshot {
return nil, be, err }
// loadMatching returns the newest snapshot where matchFn returns true.
func (s *Snapshotter) loadMatching(matchFn func(*raftpb.Snapshot) bool) (*raftpb.Snapshot, error) {
names, err := s.snapNames() // 获取所有快照数据文件的名字
if err != nil {
return nil, err }
var snap *raftpb.Snapshot
for _, name := range names {
if snap, err = loadSnap(s.lg, s.dir, name); err == nil && matchFn(snap) {
return snap, nil } } // 调用loadSnap函数加载快照数据,并调用matchFn对快照数据中包含的元数据和snapshotType的wal日志条目进行比较匹配
return nil, ErrNoSnapshot
}
// LoadNewestAvailable loads the newest snapshot available that is in walSnaps.
func (s *Snapshotter) LoadNewestAvailable(walSnaps []walpb.Snapshot) (*raftpb.Snapshot, error) {
return s.loadMatching(func(snapshot *raftpb.Snapshot) bool {
m := snapshot.Metadata
for i := len(walSnaps) - 1; i >= 0; i-- {
if m.Term == walSnaps[i].Term && m.Index == walSnaps[i].Index {
return true } } // 比较快照数据的元数据包含的任期、raft log pos和snapshotType的wal日志条目对应任期和raft log pos是否一致
return false
})
}
recoverSnapshot函数开始使用快照恢复v2 store和v3 store。st.Recovery函数用于使用快照数据恢复v2存储,serverstorage.RecoverSnapshotBackend函数用于恢复v3 store。v2存储的恢复其实就是加载快照文件中的JSON数据。v3存储恢复会检测前面创建的Backend实例是否可用(即包含了快照数据所包含的全部Entry记录),如果可用则继续使用该Backend实例,如果不可用则根据快照的元数据查找可用的BoltDB数据文件,并创建新的Backend实例。
if snapshot != nil {
if err = st.Recovery(snapshot.Data); err != nil {
cfg.Logger.Panic("failed to recover from snapshot", zap.Error(err)) } // 使用快照数据恢复v2存储
if err = serverstorage.AssertNoV2StoreContent(cfg.Logger, st, cfg.V2Deprecation); err != nil {
cfg.Logger.Error("illegal v2store content", zap.Error(err))
return nil, be, err
}
if be, err = serverstorage.RecoverSnapshotBackend(cfg, be, *snapshot, beExist, beHooks); err != nil {
cfg.Logger.Panic("failed to recover v3 backend from snapshot", zap.Error(err)) } // 使用快照恢复v3存储
ci.SetBackend(be) // A snapshot db may have already been recovered, and the old db should have already been closed in this case, so we should set the backend again.
if beExist {
// TODO: remove kvindex != 0 checking when we do not expect users to upgrade etcd from pre-3.0 release.
kvindex := ci.ConsistentIndex()
if kvindex < snapshot.Metadata.Index {
if kvindex != 0 {
return nil, be, fmt.Errorf("database file (%v index %d) does not match with snapshot (index %d)", cfg.BackendPath(), kvindex, snapshot.Metadata.Index)}
)
}
}
} else {
cfg.Logger.Info("No snapshot found. Recovering WAL from scratch!") // 没有快照 }
return snapshot, be, nil
}
恢复v2 store Recovery
Recovery函数定义在server/etcdserver/api/v2store/store.go中,其代码如下所示。从上面调用逻辑可以看出其是从snapshot.Data字段中序列化出json格式的数据作为store的数据。从静态存储系统恢复它需要恢复节点的父字段。 它需要删除自节省时间以来过期的节点,还需要创建监控 goroutines。这里不对细节进行讲解,因为本博客不会关注于v2store的原理,详细内容可以参见《etcd技术内幕》的storage章节。
func (s *store) Recovery(state []byte) error {
s.worldLock.Lock()
defer s.worldLock.Unlock()
err := json.Unmarshal(state, s)
if err != nil {
return err }
s.ttlKeyHeap = newTtlKeyHeap()
s.Root.recoverAndclean()
return nil
}
etcd v2版本存储是完全的内存实现,其数据以树形结构的方式维护在内存中。其持久化方式是将整个存储的数据序列化成JSON格式的数据,并写入磁盘文件中。
恢复v3 store RecoverSnapshotBackend
RecoverSnapshotBackend函数定义在server/storage/backend.go文件中,其会检测前面创建的Backend实例是否可用(即包含了快照数据所包含的全部Entry记录),如果可用则继续使用该Backend实例,如果不可用则根据快照的元数据查找可用的BoltDB数据库文件,并创建新的Backend实例。
func RecoverSnapshotBackend(cfg config.ServerConfig, oldbe backend.Backend, snapshot raftpb.Snapshot, beExist bool, hooks *BackendHooks) (backend.Backend, error) {
consistentIndex := uint64(0)
if beExist {
consistentIndex, _ = schema.ReadConsistentIndex(oldbe.ReadTx()) } // 获取cInde值
if snapshot.Metadata.Index <= consistentIndex {
return oldbe, nil } // 若当前使用的Backend实例可用,则直接返回
oldbe.Close() // 酒Backend实例不可用,则关闭旧的Backend实例,并重建新的Backend实例
return OpenSnapshotBackend(cfg, snap.New(cfg.Logger, cfg.SnapDir()), snapshot, hooks)
}
// OpenSnapshotBackend renames a snapshot db to the current etcd db and opens it.
func OpenSnapshotBackend(cfg config.ServerConfig, ss *snap.Snapshotter, snapshot raftpb.Snapshot, hooks *BackendHooks) (backend.Backend, error) {
snapPath, err := ss.DBFilePath(snapshot.Metadata.Index) //根据快照元数据查找对应的BoltDB数据库文件
if err != nil {
return nil, fmt.Errorf("failed to find database snapshot file (%v)", err) }
if err := os.Rename(snapPath, cfg.BackendPath()); err != nil {
return nil, fmt.Errorf("failed to rename database snapshot file (%v)", err) } // 将可用的BoltDB数据库文件移动到指定的目录中
return OpenBackend(cfg, hooks), nil
}
边栏推荐
- 动态内存管理
- 二进制安装 mysql 初始化密码问题
- [Hongke] Introduction to genicam protocol
- OGG中token的使用
- 软件测试需要学习多久?
- cut,sort,uniq,xargs
- 如何在docker里搭建自己的云数据库
- 解决ApplicationEventMulticaster not initialized - call ‘refresh‘ before multicasting events异常
- Use torch NN builds the simplest neural network framework
- Project code training tutorial
猜你喜欢
解决ApplicationEventMulticaster not initialized - call ‘refresh‘ before multicasting events异常
【回归预测】基于粒子滤波实现锂离子电池寿命预测附matlab代码
Hcip - Comprehensive Experiment of OSPF
二、品达通用权限系统__项目搭建
MySQL读写分离
AuthTalk 第一期预告 | 全面拆解多租户解决方案
解决接口跨域问题和node操作MySQL
HCIP --- OSPF的综合实验
Matlab imports floating-point numbers with more than 9 digits after the decimal point
项目代码训练教程
随机推荐
Google Play应用商店可能会删除应用权限概述 转而使用新的数据安全信息组合
MySQL8下的JSON
终结重复开发,两三下搞定登录系统个性化
Jsp+servlet+mysql案例
LeetCode 0115. Different subsequences
LeetCode 剑指 Offer II 041. 滑动窗口的平均值:低空间消耗解决
Use torch NN builds the simplest neural network framework
Programming in the novel [serial 11] the moon bends in the yuan universe
QR分解求矩阵逆--c工程实现
对ogg用户进行加密
SDL图像显示
Cocos shader basics 7
How to set preferences when developing esp8266 and esp32 with Arduino
MySQL索引(三)
sql server建表时设置ID字段自增 (navicat 演示)
什么是内存溢出
SQL优化
Nacos new configuration management
Graphite thickness measurement
Exchange array elements without creating temporary variables