SoFunction
Updated on 2025-03-05

A detailed explanation of how GO realizes AOF persistence of Redis

GO realizes AOF persistence of Redis

Store the instructions sent by the user in the local AOF file in the form of RESP protocol. After restarting Redis, perform this file to restore data.

/csgopher/go-redis

This article involves the following files:: Configuration files

aof: implement aof

appendonly yes
appendfilename

aof/

type CmdLine = [][]byte

const (
   aofQueueSize = 1 << 16
)

type payload struct {
   cmdLine CmdLine
   dbIndex int
}

type AofHandler struct {
   db          
   aofChan     chan *payload
   aofFile     *
   aofFilename string
   currentDB   int
}

func NewAOFHandler(db ) (*AofHandler, error) {
   handler := &AofHandler{}
    = 
    = db
   ()
   aofFile, err := (, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600)
   if err != nil {
      return nil, err
   }
    = aofFile
    = make(chan *payload, aofQueueSize)
   go func() {
      ()
   }()
   return handler, nil
}

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

func (handler *AofHandler) handleAof() {
    = 0
   for p := range  {
      if  !=  {
         // select db
         data := (("SELECT", ())).ToBytes()
         _, err := (data)
         if err != nil {
            (err)
            continue
         }
          = 
      }
      data := ().ToBytes()
      _, err := (data)
      if err != nil {
         (err)
      }
   }
}

func (handler *AofHandler) LoadAof() {
   file, err := ()
   if err != nil {
      (err)
      return
   }
   defer ()
   ch := (file)
   fakeConn := &{}
   for p := range ch {
      if  != nil {
         if  ==  {
            break
         }
         ("parse error: " + ())
         continue
      }
      if  == nil {
         ("empty payload")
         continue
      }
      r, ok := .(*)
      if !ok {
         ("require multi bulk reply")
         continue
      }
      ret := (fakeConn, )
      if (ret) {
         ("exec err", err)
      }
   }
}
  • AofHandler: 1. Receive data from the pipeline 2. Write to the AOF file
  • AddAof: User's instructions are packaged into payload and put into the pipeline
  • handleAof: Write payload in the pipeline to disk
  • LoadAof: Load aof file after restarting Redis

database/

type Database struct {
   dbSet []*DB
   aofHandler *
}

func NewDatabase() *Database {
   mdb := &Database{}
   if  == 0 {
       = 16
   }
    = make([]*DB, )
   for i := range  {
      singleDB := makeDB()
       = i
      [i] = singleDB
   }
   if  {
      aofHandler, err := (mdb)
      if err != nil {
         panic(err)
      }
       = aofHandler
      for _, db := range  {
         singleDB := db
          = func(line CmdLine) {
            (, line)
         }
      }
   }
   return mdb
}

Add AOF to database

Reasons to use singleDB: Because the address of the return variable in the loop is exactly the same, when we want to access the address where the element in the array is, we should not directly obtain the variable address db returned by range, but should use singleDB := db

database/

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

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

Since the database db cannot reference aof, add an anonymous function of addAof and use this anonymous function to call AddAof in NewDatabase

database/

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

func execFlushDB(db *DB, args [][]byte)  {
	()
	(utils.ToCmdLine2("flushdb", args...))
	return &{}
}

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

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

database/

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

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

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

   entity, exists := (key)
   (key, &{Data: value})
   (utils.ToCmdLine2("getset", args...))
   ......
}

Add addAof method

Test command

*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

This is the end of this article about how GO realizes AOF persistence of Redis. For more related GO to realize Redis AOF persistence, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!