GO實現Redis:GO實現Redis的AOF持久化(4)

来源:https://www.cnblogs.com/csgopher/archive/2023/03/26/17249337.html
-Advertisement-
Play Games

將用戶發來的指令以RESP協議的形式存儲在本地的AOF文件,重啟Redis後執行此文件恢複數據 https://github.com/csgopher/go-redis 本文涉及以下文件: redis.conf:配置文件 aof:實現aof redis.conf appendonly yes app ...


  • 將用戶發來的指令以RESP協議的形式存儲在本地的AOF文件,重啟Redis後執行此文件恢複數據
  • https://github.com/csgopher/go-redis
  • 本文涉及以下文件:
    redis.conf:配置文件
    aof:實現aof

redis.conf

appendonly yes
appendfilename appendonly.aof

aof/aof.go

type CmdLine = [][]byte

const (
   aofQueueSize = 1 << 16
)

type payload struct {
   cmdLine CmdLine
   dbIndex int
}

type AofHandler struct {
   db          databaseface.Database
   aofChan     chan *payload
   aofFile     *os.File
   aofFilename string
   currentDB   int
}

func NewAOFHandler(db databaseface.Database) (*AofHandler, error) {
   handler := &AofHandler{}
   handler.aofFilename = config.Properties.AppendFilename
   handler.db = db
   handler.LoadAof()
   aofFile, err := os.OpenFile(handler.aofFilename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600)
   if err != nil {
      return nil, err
   }
   handler.aofFile = aofFile
   handler.aofChan = make(chan *payload, aofQueueSize)
   go func() {
      handler.handleAof()
   }()
   return handler, nil
}

func (handler *AofHandler) AddAof(dbIndex int, cmdLine CmdLine) {
   if config.Properties.AppendOnly && handler.aofChan != nil {
      handler.aofChan <- &payload{
         cmdLine: cmdLine,
         dbIndex: dbIndex,
      }
   }
}

func (handler *AofHandler) handleAof() {
   handler.currentDB = 0
   for p := range handler.aofChan {
      if p.dbIndex != handler.currentDB {
         // select db
         data := reply.MakeMultiBulkReply(utils.ToCmdLine("SELECT", strconv.Itoa(p.dbIndex))).ToBytes()
         _, err := handler.aofFile.Write(data)
         if err != nil {
            logger.Warn(err)
            continue
         }
         handler.currentDB = p.dbIndex
      }
      data := reply.MakeMultiBulkReply(p.cmdLine).ToBytes()
      _, err := handler.aofFile.Write(data)
      if err != nil {
         logger.Warn(err)
      }
   }
}

func (handler *AofHandler) LoadAof() {
   file, err := os.Open(handler.aofFilename)
   if err != nil {
      logger.Warn(err)
      return
   }
   defer file.Close()
   ch := parser.ParseStream(file)
   fakeConn := &connection.Connection{}
   for p := range ch {
      if p.Err != nil {
         if p.Err == io.EOF {
            break
         }
         logger.Error("parse error: " + p.Err.Error())
         continue
      }
      if p.Data == nil {
         logger.Error("empty payload")
         continue
      }
      r, ok := p.Data.(*reply.MultiBulkReply)
      if !ok {
         logger.Error("require multi bulk reply")
         continue
      }
      ret := handler.db.Exec(fakeConn, r.Args)
      if reply.IsErrorReply(ret) {
         logger.Error("exec err", err)
      }
   }
}

AofHandler:1.從管道中接收數據 2.寫入AOF文件
AddAof:用戶的指令包裝成payload放入管道
handleAof:將管道中的payload寫入磁碟
LoadAof:重啟Redis後載入aof文件

database/database.go

type Database struct {
   dbSet []*DB
   aofHandler *aof.AofHandler
}

func NewDatabase() *Database {
   mdb := &Database{}
   if config.Properties.Databases == 0 {
      config.Properties.Databases = 16
   }
   mdb.dbSet = make([]*DB, config.Properties.Databases)
   for i := range mdb.dbSet {
      singleDB := makeDB()
      singleDB.index = i
      mdb.dbSet[i] = singleDB
   }
   if config.Properties.AppendOnly {
      aofHandler, err := aof.NewAOFHandler(mdb)
      if err != nil {
         panic(err)
      }
      mdb.aofHandler = aofHandler
      for _, db := range mdb.dbSet {
         singleDB := db
         singleDB.addAof = func(line CmdLine) {
            mdb.aofHandler.AddAof(singleDB.index, line)
         }
      }
   }
   return mdb
}

將AOF加入到database里
使用singleDB的原因:因為在迴圈中獲取返回變數的地址都完全相同,因此當我們想要訪問數組中元素所在的地址時,不應該直接獲取 range 返回的變數地址 db,而應該使用 singleDB := db

database/db.go

type DB struct {
   index int
   data   dict.Dict
   addAof func(CmdLine)
}

func makeDB() *DB {
	db := &DB{
		data:   dict.MakeSyncDict(),
		addAof: func(line CmdLine) {},
	}
	return db
}

由於分資料庫db引用不到aof,所以添加一個addAof匿名函數,在NewDatabase中用這個匿名函數調用AddAof

database/keys.go

func execDel(db *DB, args [][]byte) resp.Reply {
   ......
   if deleted > 0 {
      db.addAof(utils.ToCmdLine2("del", args...))
   }
   return reply.MakeIntReply(int64(deleted))
}

func execFlushDB(db *DB, args [][]byte) resp.Reply {
	db.Flush()
	db.addAof(utils.ToCmdLine2("flushdb", args...))
	return &reply.OkReply{}
}

func execRename(db *DB, args [][]byte) resp.Reply {
	......
	db.addAof(utils.ToCmdLine2("rename", args...))
	return &reply.OkReply{}
}

func execRenameNx(db *DB, args [][]byte) resp.Reply {
	......
	db.addAof(utils.ToCmdLine2("renamenx", args...))
	return reply.MakeIntReply(1)
}

database/string.go

func execSet(db *DB, args [][]byte) resp.Reply {
   ......
   db.addAof(utils.ToCmdLine2("set", args...))
   return &reply.OkReply{}
}

func execSetNX(db *DB, args [][]byte) resp.Reply {
   ......
   db.addAof(utils.ToCmdLine2("setnx", args...))
   return reply.MakeIntReply(int64(result))
}

func execGetSet(db *DB, args [][]byte) resp.Reply {
   key := string(args[0])
   value := args[1]

   entity, exists := db.GetEntity(key)
   db.PutEntity(key, &database.DataEntity{Data: value})
   db.addAof(utils.ToCmdLine2("getset", args...))
   ......
}

添加addAof方法

測試命令

*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n
*2\r\n$6\r\nSELECT\r\n$1\r\n1\r\n

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文使用簡單易懂的代碼,實現了一組可以構造解析器的函數。相信通過本文的演示,你應該對解析器的基本工作原理有了一個淺淺的瞭解。 ...
  • 計算屬性模式(Computed Property Pattern):在JavaScript中,可以使用Object.defineProperty()方法來實現計算屬性模式,通過get和set方法來計算屬性值。 計算屬性模式用於將對象的某些屬性值與其他屬性值相關聯。該模式常用於Vue.js等框架中。 ...
  • 終端主要的目的是可以讓我們在文件系統中進行四處瀏覽和打開/運行某些東西,只是在 windows 上我們之前都是用 GUI 界面進行操作的,現在是在終端中使用 shell 命令進行操作而已。 ...
  • UML(Unified Modeling Language)統一建模語言 power designer中給類的“操作”添加“輸入參數” 查看所設計類的代碼: 類和類的六種關係 1 繼承關係(泛化關係) 語義:子類和父類之間的關係 語法:extends 符號:(子類指向父類) power design ...
  • 代理模式(Proxy Pattern)是一種結構型設計模式,結構型模式描述如何將類或對象按某種佈局組成更大的結構。它允許你提供一個代理對象來控制對另一個對象的訪問。代理對象擁有與實際對象相同的介面,因此它可以被用來代替實際對象。 ...
  • 1. JVM線程優化 1.1. 當空間不足時,可以調整線程使用的記憶體 1.2. 每個線程都有一個原生棧,操作系統會在這裡存儲線程的調用棧信息 1.3. 原生棧的大小是1 MB 1.3.1. 32位的Windows JVM原生棧大小是320KB 1.3.2. 在64位的JVM中,通常不會修改這個值 1 ...
  • 1. 下載jsoncpp -->https://github.com/open-source-parsers/jsoncpp/tree/update 兩種下載方法: 方法一:git clone ... 到伺服器上(或虛擬機上...),有點慢,甚至會失敗...! 方法二:下載 zip包,通過第三方軟體 ...
  • 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹在 QT 中使用 VLD 時,有多處記憶體泄漏時的輸出報告解析。 ...
一周排行
    -Advertisement-
    Play Games
  • C#.Net的BCL提供了豐富的類型,最基礎的是值類型、引用類型,而他們的共同(隱私)祖先是 System.Object(萬物之源),所以任何類型都可以轉換為Object。 ...
  • 最近有群友咨詢C#如何調用Python?小編嘗試Python.NET過程中遭遇的版本相容性和環境配置難題,小編決定尋找一個更為簡單、穩定且對初學者友好的解決方案。小編搜索一番,除了Python.NET之外,還有其他途徑能夠幫助我們輕鬆地在C#項目調用Python腳本,那就是通過命令行調用,使用 Sy ...
  • .NET中特性+反射 實現數據校驗 在.NET中,我們可以使用特性+反射來實現數據校驗。特性是一種用於為程式中的代碼添加元數據的機制。元數據是與程式中的代碼相關聯的數據,但不直接成為代碼的一部分。通過特性,我們可以為類、方法、屬性等添加額外的信息,這些信息可以在運行時通過反射獲取和使用。 對反射不太 ...
  • Biwen.Settings 是一個簡易的配置項管理模塊,主要的作用就是可以校驗並持久化配置項,比如將自己的配置存儲到資料庫中,JSON文件中等 使用上也是很簡單,只需要在服務中註入配置, 比如我們有一個GithubSetting的配置項,我們只需要定義好對象然後註入到Service中即可: [De ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 前言 VB.NET,全名Visual Basic .NET,是Microsoft .NET框架的一部分,是一種面向對象的編程語言。它繼承了Visual Basic的易用性,同時增加了對面向對象編程的支持。VB.NET提供了大量的內置函數,使得開發者可以更容易地處理字元串、數學計算、文件和目錄訪問等任 ...
  • 自定義可移動點二維坐標軸控制項 目錄 路由參數 坐標軸控制項定義 Demo 路由參數 X_YResultCollection為當前X軸對應Y軸值存儲字典 public class ResultCollectionChangedEventArgs(RoutedEvent routedEvent, obje ...
  • 自定義分頁控制項 tip: 該控制項的樣式用的是materialDesign庫,需要下載Nuget包 Code Xaml <UserControl x:Class="TestTool.CustomControls.PagingControl" xmlns="http://schemas.microsof ...
  • 最近群里有個小伙伴把Dapper遷移SqlSugar幾個不能解決的問題進行一個彙總,我正好寫一篇文章來講解一下 一、sql where in傳參問題: SELECT * FROM users where id IN @ids 答: SqlSugar中應該是 var sql="SELECT * FRO ...
  • 安裝nuget包 Wesky.Net.OpenTools 1.0.8或以上版本。支持.net framework 4.6以上版本,以及所有.net core以及以上版本引用。 開發一個簡單的Winform界面,用來測試使用。如需該winform的demo,可以在公眾號【Dotnet Dancer】後 ...