最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

探索Golang實現(xiàn)Redis持久化AOF實例

 更新時間:2024年01月24日 09:38:59   作者:紹納?nullbody筆記  
這篇文章主要為大家介紹了Golang實現(xiàn)Redis持久化AOF實例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

用11篇文章實現(xiàn)一個可用的Redis服務(wù),姑且叫EasyRedis吧,希望通過文章將Redis掰開撕碎了呈現(xiàn)給大家,而不是僅僅停留在八股文的層面,并且有非常爽的感覺,歡迎持續(xù)關(guān)注學(xué)習(xí)。

[x] easyredis之TCP服務(wù)

[x] easyredis之網(wǎng)絡(luò)請求序列化協(xié)議(RESP)

[x] easyredis之內(nèi)存數(shù)據(jù)庫

[x] easyredis之過期時間 (時間輪實現(xiàn))

[x] easyredis之持久化 (AOF實現(xiàn))

[ ] easyredis之發(fā)布訂閱功能

[ ] easyredis之有序集合(跳表實現(xiàn))

[ ] easyredis之 pipeline 客戶端實現(xiàn)

[ ] easyredis之事務(wù)(原子性/回滾)

[ ] easyredis之連接池

[ ] easyredis之分布式集群存儲

【第五篇】EasyRedis之持久化AOF

AOF全稱Append Only File,就是將寫相關(guān)的命令,追加保存到文件中,當(dāng)服務(wù)器重啟以后,將文件中的命令在服務(wù)端重放(重新執(zhí)行恢復(fù)數(shù)據(jù)),實現(xiàn)的一種持久化方式。

本篇通過3個部分講解AOF的實現(xiàn):

  • AOF的寫入過程

  • AOF的加載過程

  • AOF的重寫過程

AOF的寫入過程

在核心的數(shù)據(jù)結(jié)構(gòu) Engine中新增一個 aof *AOF對象

// 存儲引擎,負責(zé)數(shù)據(jù)的CRUD
type Engine struct {
	// *DB
	dbSet []*atomic.Value
	// 時間輪(延遲任務(wù))
	delay *timewheel.Delay
	// Append Only File
	aof *aof.AOF
}

在初始化函數(shù)func NewEngine() *Engine中,會基于是否啟用AOF日志,決定 aof *aof.AOF的初始化

func NewEngine() *Engine {
	//.....省略....
	// 啟用AOF日志
	if conf.GlobalConfig.AppendOnly {
		// 創(chuàng)建*AOF對象
		aof, err := aof.NewAOF(conf.GlobalConfig.AppendFilename, engine, true, conf.GlobalConfig.AppendFsync)
		if err != nil {
			panic(err)
		}
		engine.aof = aof
		// 設(shè)定每個db,使用aof寫入日志
		engine.aofBindEveryDB()
	}
	return engine
}

因為實際執(zhí)行redis命令的對象是 *DB,所以會對每個*DB對象設(shè)定db.writeAof函數(shù)指針

func (e *Engine) aofBindEveryDB() {
	for _, dbSet := range e.dbSet {
		db := dbSet.Load().(*DB)
		db.writeAof = func(redisCommand [][]byte) {
			if conf.GlobalConfig.AppendOnly {
                // 調(diào)用e.aof對象方法,保存命令
				e.aof.SaveRedisCommand(db.index, aof.Command(redisCommand))
			}
		}
	}
}

例如,當(dāng)我們執(zhí)行 set key value命令的時候,實際會執(zhí)行 func cmdSet(db *DB, args [][]byte) protocol.Reply

func cmdSet(db *DB, args [][]byte) protocol.Reply {
    //.....省略....
	if result > 0 { // 1 表示存儲成功
		//TODO: 過期時間處理
		if ttl != nolimitedTTL { // 設(shè)定key過期
			expireTime := time.Now().Add(time.Duration(ttl) * time.Millisecond)
			db.ExpireAt(key, expireTime)
			//寫入日志
			db.writeAof(aof.SetCmd([][]byte{args[0], args[1]}...))
			db.writeAof(aof.PExpireAtCmd(string(args[0]), expireTime))
		} else { // 設(shè)定key不過期
			db.Persist(key)
			//寫入日志
			db.writeAof(aof.SetCmd(args...))
		}
		return protocol.NewOkReply()
	}
	return protocol.NewNullBulkReply()
}

可以看到,會調(diào)用上面剛才設(shè)定的db.writeAof函數(shù),將當(dāng)前的命令保存到AOF中。所以我們實際看下 SaveRedisCommand函數(shù)中具體在做什么事情。代碼路徑位于aof/aof.go

func (aof *AOF) SaveRedisCommand(dbIndex int, command Command) {
	// 關(guān)閉
	if aof.atomicClose.Load() {
		return
	}
	// 寫入文件 & 刷盤
	if aof.aofFsync == FsyncAlways {
		record := aofRecord{
			dbIndex: dbIndex,
			command: command,
		}
		aof.writeAofRecord(record)
		return
	}
	// 寫入緩沖
	aof.aofChan <- aofRecord{
		dbIndex: dbIndex,
		command: command,
	}
}

因為AOF的刷盤(Sync)有三種模式:

  • 寫入 & 立即刷盤

  • 寫入 & 每秒刷盤

  • 寫入 & 不主動刷盤(取決于操作系統(tǒng)自動刷盤)

如果配置的是always模式,會立即執(zhí)行aof.writeAofRecord(record);否則就將數(shù)據(jù)先保存在緩沖aof.aofChan中(這里其實又是生產(chǎn)者消費者模型)最后在消費協(xié)程中,執(zhí)行寫入

func (aof *AOF) watchChan() {
	for record := range aof.aofChan {
		aof.writeAofRecord(record)
	}
	aof.aofFinished <- struct{}{}
}

所以我們只需要看下 writeAofRecord函數(shù)即可,其實就是把命令按照Redis 序列化協(xié)議的格式,寫入到文件中。給大家看下更直觀的演示圖:

再看下在 append.aof文件中具體的數(shù)據(jù)格式:

這里有個很重要點:因為AOF文件是所有的*DB對象復(fù)用的文件,寫入的redis命令歸屬于不同的數(shù)據(jù)庫的

舉個例子: 比如在0號數(shù)據(jù)庫,我們執(zhí)行set key value,在3號數(shù)據(jù)庫,我們執(zhí)行set key value,在日志文件中會記錄兩條命令,但是這兩個命令其實是不同數(shù)據(jù)庫的命令。在恢復(fù)命令到數(shù)據(jù)庫的時候,應(yīng)該在不同的數(shù)據(jù)庫中執(zhí)行該命令。所以在記錄命令的時候,我們還要記錄下他的數(shù)據(jù)庫是什么?這樣恢復(fù)的時候,才能知道命令的數(shù)據(jù)庫的歸屬問題。

func (aof *AOF) writeAofRecord(record aofRecord) {
	aof.mu.Lock()
	defer aof.mu.Unlock()
	// 因為aof對象是所有數(shù)據(jù)庫對象【復(fù)用】寫入文件方法,每個數(shù)據(jù)庫的索引不同
	// 所以,每個命令的執(zhí)行,有個前提就是操作的不同的數(shù)據(jù)庫
	if record.dbIndex != aof.lastDBIndex {
		// 構(gòu)建select index 命令 & 寫入文件
		selectCommand := [][]byte{[]byte("select"), []byte(strconv.Itoa(record.dbIndex))}
		data := protocol.NewMultiBulkReply(selectCommand).ToBytes()
		_, err := aof.aofFile.Write(data)
		if err != nil {
			logger.Warn(err)
			return
		}
		aof.lastDBIndex = record.dbIndex
	}
	// redis命令
	data := protocol.NewMultiBulkReply(record.command).ToBytes()
	_, err := aof.aofFile.Write(data)
	if err != nil {
		logger.Warn(err)
	}
	logger.Debugf("write aof command:%q", data)
	// 每次寫入刷盤
	if aof.aofFsync == FsyncAlways {
		aof.aofFile.Sync()
	}
}

AOF的加載過程

在服務(wù)啟動的時候,將*.aof文件中的命令,在服務(wù)端進行重放,效果演示如下:

代碼路徑位于aof/aof.go

// 構(gòu)建AOF對象
func NewAOF(aofFileName string, engine abstract.Engine, load bool, fsync string) (*AOF, error) {
	//...省略...
	// 啟動加載aof文件
	if load {
		aof.LoadAof(0)
	}
    //...省略...
}

aof.LoadAof(0)函數(shù)的本質(zhì)就是從文件中,按照行讀取數(shù)據(jù)。如果看過之前的文章,這里其實復(fù)用了parser.ParseStream(reader)函數(shù),負責(zé)從文件解析redis序列化協(xié)議格式的命令,最后利用數(shù)據(jù)庫引擎,將命令數(shù)據(jù)保存到內(nèi)存中(命令重放)

func (aof *AOF) LoadAof(maxBytes int) {
	// 目的:當(dāng)加載aof文件的時候,因為需要復(fù)用engine對象,內(nèi)部重放命令的時候會自動寫aof日志,加載aof 禁用 SaveRedisCommand的寫入
	aof.atomicClose.Store(true)
	deferfunc() {
		aof.atomicClose.Store(false)
	}()
	// 只讀打開文件
	file, err := os.Open(aof.aofFileName)
	if err != nil {
		logger.Error(err.Error())
		return
	}
	defer file.Close()
	file.Seek(0, io.SeekStart)
	var reader io.Reader
	if maxBytes > 0 { // 限定讀取的字節(jié)大小
		reader = io.LimitReader(file, int64(maxBytes))
	} else { // 不限定,直接讀取到文件結(jié)尾(為止)
		reader = file
	}
	// 文件中保存的格式和網(wǎng)絡(luò)傳輸?shù)母袷揭恢?
	ch := parser.ParseStream(reader)
	virtualConn := connection.NewVirtualConn()
	for payload := range ch {
		if payload.Err != nil {
			// 文件已經(jīng)讀取到“完成“
			if payload.Err == io.EOF {
				break
			}
			// 讀取到非法的格式
			logger.Errorf("LoadAof parser error %+v:", payload.Err)
			continue
		}
		if payload.Reply == nil {
			logger.Error("empty payload data")
			continue
		}
		// 從文件中讀取到命令
		reply, ok := payload.Reply.(*protocol.MultiBulkReply)
		if !ok {
			logger.Error("require multi bulk protocol")
			continue
		}
		// 利用數(shù)據(jù)庫引擎,將命令數(shù)據(jù)保存到內(nèi)存中(命令重放)
		ret := aof.engine.Exec(virtualConn, reply.RedisCommand)
		// 判斷是否執(zhí)行失敗
		if protocol.IsErrReply(ret) {
			logger.Error("exec err ", string(ret.ToBytes()))
		}
		// 判斷命令是否是"select"
		if strings.ToLower(string(reply.RedisCommand[0])) == "select" {
			dbIndex, err := strconv.Atoi(string(reply.RedisCommand[1]))
			if err == nil {
				aof.lastDBIndex = dbIndex // 記錄下數(shù)據(jù)恢復(fù)過程中,選中的數(shù)據(jù)庫索引
			}
		}
	}
}

AOF的重寫過程

代碼路徑aof/rewrite.go重寫的過程就是下面的函數(shù)

func (aof *AOF) Rewrite(engine abstract.Engine) {
	//1.對現(xiàn)有的aof文件做一次快照
	snapShot, err := aof.startRewrite()
	if err != nil {
		logger.Errorf("StartRewrite err: %+v", err)
		return
	}
	//2. 將現(xiàn)在的aof文件數(shù)據(jù),加在到新(內(nèi)存)對象中,并重寫入新aof文件中
	err = aof.doRewrite(snapShot, engine)
	if err != nil {
		logger.Errorf("doRewrite err: %+v", err)
		return
	}
	//3. 將重寫過程中的增量命令寫入到新文件中
	err = aof.finishRewrite(snapShot)
	if err != nil {
		logger.Errorf("finishRewrite err: %+v", err)
	}
}

整個的處理思想很重要:如下圖

總結(jié)

代碼的思路應(yīng)該還是比較清晰,但是細節(jié)上的處理非常容易讓人大腦宕機。建議還是看下源碼,邊看邊自己敲一下,感受是不一樣

項目代碼地址: https://github.com/gofish2020/easyredis 

以上就是探索Golang實現(xiàn)Redis持久化AOF實例的詳細內(nèi)容,更多關(guān)于Golang Redis持久化AOF的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語句與表達式案例手冊深度解析

    Go語句與表達式案例手冊深度解析

    這篇文章主要為大家介紹了Go語句與表達式案例手冊深度解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • Go高級特性探究之HTTP錯誤處理詳解

    Go高級特性探究之HTTP錯誤處理詳解

    在Web應(yīng)用程序中,HTTP錯誤處理是非常重要的,它關(guān)系到Web應(yīng)用程序的穩(wěn)定性和可靠性,本文介紹如何在Go項目中處理HTTP錯誤,并提供相應(yīng)的解決方案和實踐經(jīng)驗,希望對Go語言Web應(yīng)用程序的開發(fā)者有所幫助
    2023-06-06
  • 解決Golang 中使用WaitGroup的那點坑

    解決Golang 中使用WaitGroup的那點坑

    這篇文章主要介紹了解決Golang 中使用WaitGroup的那點坑,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Go基礎(chǔ)教程系列之Go接口使用詳解

    Go基礎(chǔ)教程系列之Go接口使用詳解

    這篇文章主要介紹了Go基礎(chǔ)教程系列之Go接口使用詳解,需要的朋友可以參考下
    2022-04-04
  • Go如何實現(xiàn)兩個程序遠程調(diào)用

    Go如何實現(xiàn)兩個程序遠程調(diào)用

    net/rpc是Go語言中用于實現(xiàn)遠程過程調(diào)用的庫,允許兩個Go程序之間直接調(diào)用遠程函數(shù),本文主要介紹了Go如何實現(xiàn)兩個程序遠程調(diào)用,具有一定的參考價值,感興趣的可以了解一下
    2026-03-03
  • Golang解析yaml文件的方法小結(jié)

    Golang解析yaml文件的方法小結(jié)

    Go 語言沒有內(nèi)置解析 yaml 文件的功能,實現(xiàn) yaml 的解析可以使用第三方庫,下面我們就來看看如何使用opkg.in/yaml.v2 和 gopkg.in/yaml.v3實現(xiàn)解析yaml吧
    2024-11-11
  • 一文詳解Golang中的匿名變量

    一文詳解Golang中的匿名變量

    匿名變量是一種特殊類型的變量,可以簡化代碼并提高可讀性,本文將為大家詳細介紹一下golang中匿名變量的定義、特性和使用方法,需要的可以參考下
    2023-09-09
  • GoLand安裝與環(huán)境配置的完整步驟

    GoLand安裝與環(huán)境配置的完整步驟

    作為一個go語言程序員,覺得自己有義務(wù)為go新手開一條更簡單便捷的上手之路,下面這篇文章主要給大家介紹了關(guān)于GoLand安裝與環(huán)境配置的完整步驟,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2022-12-12
  • Go+Lua解決Redis秒殺中庫存與超賣問題

    Go+Lua解決Redis秒殺中庫存與超賣問題

    本文主要介紹了Go+Lua解決Redis秒殺中庫存與超賣問題,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • golang實現(xiàn)sql結(jié)果集以json格式輸出的方法

    golang實現(xiàn)sql結(jié)果集以json格式輸出的方法

    這篇文章主要介紹了golang實現(xiàn)sql結(jié)果集以json格式輸出的方法,涉及Go語言針對sql結(jié)果集的遍歷、轉(zhuǎn)換及json格式相關(guān)操作技巧,需要的朋友可以參考下
    2017-03-03

最新評論

汤原县| 宁海县| 乾安县| 托里县| 邛崃市| 板桥市| 秦安县| 健康| 宜兰市| 集安市| 松桃| 岳普湖县| 阿鲁科尔沁旗| 长寿区| 泾源县| 阿勒泰市| 澎湖县| 麦盖提县| 江门市| 韶山市| 太和县| 阜宁县| 丰原市| 新龙县| 延吉市| 晋中市| 白朗县| 资源县| 出国| 都昌县| 东乌| 顺昌县| 永仁县| 屏南县| 巴东县| 堆龙德庆县| 平昌县| 特克斯县| 南丹县| 游戏| 鹤岗市|