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

使用go語(yǔ)言實(shí)現(xiàn)Redis持久化的示例代碼

 更新時(shí)間:2024年07月14日 08:26:11   作者:uccs  
redis 是一個(gè)內(nèi)存數(shù)據(jù)庫(kù),如果你把進(jìn)程殺掉,那么里面存儲(chǔ)的數(shù)據(jù)都會(huì)消失,那么這篇文章就是來(lái)解決 redis 持久化的問(wèn)題,本文給大家介紹了使用go語(yǔ)言實(shí)現(xiàn)Redis持久化,需要的朋友可以參考下

redis 是一個(gè)內(nèi)存數(shù)據(jù)庫(kù),如果你把進(jìn)程殺掉,那么里面存儲(chǔ)的數(shù)據(jù)都會(huì)消失,那么這篇文章就是來(lái)解決 redis 持久化的問(wèn)題

我們?cè)?nbsp;redis.conf 文件中增加兩個(gè)配置

appendonly yes
appendfilename appendonly.aof
  • appenonly 表示只追加
  • appendfilename 表示追加到那什么文件中

指令: *3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nvalue\r\n 落在 appendonly.aof 文件中

*3
$3
SET
$3
KEY
$5
value

這里要實(shí)現(xiàn)的功能就是把用戶發(fā)過(guò)來(lái)的指令,用 RESP 的形式記錄在 appendonly.aof 文件中

這個(gè)文件是在機(jī)器的硬盤上,當(dāng) redis 停了之后,內(nèi)存中的數(shù)據(jù)都沒(méi)了,但這個(gè)文件會(huì)保存下

redis 重啟后,會(huì)讀取這個(gè)文件,把之前內(nèi)存中的數(shù)據(jù)再次加載回來(lái)

定義 AofHandler

在項(xiàng)目下新建文件 aof/aof.go

在里面定義一個(gè) AofHandler 結(jié)構(gòu)體,它的作用就是用來(lái)處理 appendonly.aof 文件

type AofHandler struct {
	database    databaseface.Database // 持有 db,db 有業(yè)務(wù)核心
	aofFile     *os.File              // 持有 aof 文件
	aofFilename string                // aof 文件名
	currentDB   int                   // 當(dāng)前 db
	aofChan     chan *payload         // 寫文件的緩沖區(qū)
}

這里有注意的是 aofChan,它是寫文件的緩沖區(qū)

因?yàn)閺奈募凶x取指令,指令是非常密集的,但是將指令寫入硬盤時(shí)非常慢的,我們又不可能每次都等待指令寫完成后再去操作 redis

這時(shí)我們就把所有想寫入 aof 文件的指令放到 aofChan 中,然后在另一個(gè) goroutine 中去寫入硬盤

所以這個(gè) aofChan 的類型是 payload 結(jié)構(gòu)體

type CmdLine = [][]byte
type payload struct {
  cmdLine CmdLine // 指令
  dbIndex int     // db 索引
}

AofHandler 結(jié)構(gòu)體定義好之后,我們需要定義一個(gè) NewAofHandler 函數(shù)來(lái)初始化 AofHandler 結(jié)構(gòu)體

還需要定義一個(gè) AddAof 方法,用來(lái)往 aofChan 中添加指令

放到緩沖區(qū)之后,還需要一個(gè)方法 HandleAof 將指令寫入硬盤

最后還要實(shí)現(xiàn)一個(gè)從硬盤加載 aof 文件到內(nèi)存的的函數(shù) LoadAof

實(shí)現(xiàn) NewAofHandler

NewAofHandler 函數(shù)用來(lái)初始化 AofHandler 結(jié)構(gòu)體

func NewAofHandler(database databaseface.Database) (*AofHandler, error) {
  // 初始化 AofHandler 結(jié)構(gòu)體
  handler := &AofHandler{}
  // 從配置文件中讀取 aof 文件名
  handler.aofFilename = config.Properties.AppendFilename
  // 持有 db
  handler.database = database
  // 從硬盤加載 aof 文件
  handler.LoadAof()
  // 打開(kāi) aof 文件, 如果不存在則創(chuàng)建
  aofFile, err := os.OpenFile(handler.aofFilename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600)
  if err != nil {
    return nil, err
  }
  // 持有 aof 文件
  handler.aofFile = aofFile
  // 初始化 aofChan
  handler.aofChan = make(chan *payload, aofBufferSize)
  // 啟動(dòng)一個(gè) goroutine 處理 aofChan
  go func() {
    handler.HandleAof()
  }()
  // 返回 AofHandler 結(jié)構(gòu)體
  return handler, nil
}

實(shí)現(xiàn) AddAof

AddAof 方法用來(lái)往 aofChan 中添加指令,它不做落盤的操作

因?yàn)樵趫?zhí)行指令的時(shí)候,等待它落盤的話,效率太低了,所以我們把指令放到 aofChan 中,然后在另一個(gè) goroutine 中去處理

func (handler *AofHandler) AddAof(dbIndex int, cmdLine CmdLine) {
  // 如果配置文件中的 appendonly 為 true 并且 aofChan 不為 nil
  if config.Properties.AppendOnly && handler.aofChan != nil {
    // 往 aofChan 中添加指令
    handler.aofChan <- &payload{
      cmdLine: cmdLine,
      dbIndex: dbIndex,
    }
  }
}

實(shí)現(xiàn) HandleAof

HandleAof 方法用來(lái)處理 aofChan 中的指令,將指令寫入硬盤

currentDB 記錄的是當(dāng)前工作的 DB,如果切換了 DB,會(huì)在 aof 文件中插入 select 0 這樣切換 DB 的語(yǔ)句

func (handler *AofHandler) HandleAof() {
  // 初始化 currentDB
  handler.currentDB = 0
  // 遍歷 chan
  for p := range handler.aofChan {
    // 如果當(dāng)前 db 不等于上一次工作的 db,就要插入一條 select 語(yǔ)句
    if p.dbIndex != handler.currentDB {
      // 我們要把 select 0 編碼成 RESP 格式
      // 也就是 *2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
      data := reply.MakeMultiBulkReply(utils.ToCmdLine("SELECT", strconv.Itoa(p.dbIndex))).ToBytes()
      // 寫入 aof 文件
      _, err := handler.aofFile.Write(data)
      if err != nil {
        logger.Warn(err)
        continue
      }
      // 更新 currentDB
      handler.currentDB = p.dbIndex
    }
    // 這里是插入正常的指令
    data := reply.MakeMultiBulkReply(p.cmdLine).ToBytes()
    // 寫入 aof 文件
    _, err := handler.aofFile.Write(data)
    if err != nil {
      logger.Warn(err)
    }
  }
}

實(shí)現(xiàn) Aof 落盤功能

我們之前在實(shí)現(xiàn)指令的部分,都是直接執(zhí)行指令,現(xiàn)在我們要把指令寫入 aof 文件

我們?cè)?nbsp;StandaloneDatabase 結(jié)構(gòu)體中增加一個(gè) aofHandler 字段

type StandaloneDatabase struct {
  dbSet      []*DB
  aofHandler *aof.AofHandler // 增加落盤功能
}

然后新建 database 時(shí)需要對(duì) aofHandler 進(jìn)行初始化

func NewStandaloneDatabase() *StandaloneDatabase {
  // ...
  // 先看下配置文件中的 appendonly 是否為 true
  if config.Properties.AppendOnly {
    // 初始化 aofHandler
    aofHandler, err := aof.NewAofHandler(database)
    if err != nil {
      panic(err)
    }
    // 持有 aofHandler
    database.aofHandler = aofHandler
    // 遍歷 dbSet
    for _, db := range database.dbSet {
      // 解決閉包問(wèn)題
      sdb := db
      // 為每個(gè) db 添加 AddAof 方法
      // 這個(gè) addAof 方法是在執(zhí)行指令的時(shí)候調(diào)用的
      sdb.addAof = func(line CmdLine) {
        database.aofHandler.AddAof(sdb.index, line)
      }
    }
  }
  return database
}

這里要注意的是 addAof 方法,它是在執(zhí)行指令的時(shí)候調(diào)用的

因?yàn)槲覀冃枰谥噶钪姓{(diào)用 Addaof 函數(shù),實(shí)現(xiàn)指令寫入 aof 文件

但是在指令中,???們只能拿到 dbdb 上又沒(méi)有操作 aof 相關(guān)的方法,所以我們需要在 db 中增加一個(gè) addAof 方法

type DB struct {
	index  int           // 數(shù)據(jù)的編號(hào)
	data   dict.Dict     // 數(shù)據(jù)類型
	addAof func(CmdLine) // 每個(gè) db 都有一個(gè) addAof 方法
}

然后就在需要落盤的指令中調(diào)用 addAof 方法

DEL 方法需要記錄下來(lái),因?yàn)?nbsp;DEL 方法是刪除數(shù)據(jù)的,如果不記錄下來(lái),那么 aof 文件中的數(shù)據(jù)就會(huì)和內(nèi)存中的數(shù)據(jù)不一致

// DEL K1 K2 K3
func DEL(db *DB, args [][]byte) resp.Reply {
  deleted := db.Removes(keys...)
  // delete 大于 0 說(shuō)明有數(shù)據(jù)被刪除
  if deleted > 0 {
    db.addAof(utils.ToCmdLine2("DEL", args...))
  }
}

FLUSHDB 方法也需要記錄下來(lái),因?yàn)?nbsp;FLUSHDB 方法是刪除當(dāng)前 DB 中的所有數(shù)據(jù)

// FLUSHDB
func FLUSHDB(db *DB, args [][]byte) resp.Reply {
	db.addAof(utils.ToCmdLine2("FLUSEHDB", args...))
}

RENAME 和 RENAMENX 方法也需要記錄下來(lái),因?yàn)檫@兩個(gè)方法是修改 key 的名字

// RENAME K1 K2
func RENAME(db *DB, args [][]byte) resp.Reply {
  db.addAof(utils.ToCmdLine2("RENAME", args...))
}

// RENAMENX K1 K2
func RENAMENX(db *DB, args [][]byte) resp.Reply {
  db.addAof(utils.ToCmdLine2("RENAMENX", args...))
}

SET 和 SETNX 方法也需要記錄下來(lái),因?yàn)檫@兩個(gè)方法是設(shè)置數(shù)據(jù)的

// SET K1 v
func SET(db *DB, args [][]byte) resp.Reply {
  db.addAof(utils.ToCmdLine2("SET", args...))
}

// SETNX K1 v
func SETNX(db *DB, args [][]byte) resp.Reply {
  db.addAof(utils.ToCmdLine2("SETNX", args...))
}

GETSET 方法也需要記錄下來(lái),因?yàn)檫@個(gè)方法是設(shè)置數(shù)據(jù)的同時(shí)返回舊數(shù)據(jù)

// GETSET K1 v1
func GETSET(db *DB, args [][]byte) resp.Reply {
  db.addAof(utils.ToCmdLine2("GETSET", args...))
}

實(shí)現(xiàn) LoadAof

LoadAof 方法用來(lái)從硬盤加載 aof 文件到內(nèi)存

aof 中的指令是符合 RESP 協(xié)議的,我們就可以把這些指令當(dāng)成用戶發(fā)過(guò)來(lái)的指令,執(zhí)行就可以了

func (handler *AofHandler) LoadAof() {
  // 打開(kāi) aof 文件
  file, err := os.Open(handler.aofFilename)
  if err != nil {
    logger.Error(err)
    return
  }
  // 關(guān)閉文件
  defer func() {
    _ = file.Close()
  }()
  // 創(chuàng)建一個(gè) RESP 解析器,將 file 傳入,解析后的指令會(huì)放到 chan 中
  ch := parser.ParseStream(file)
  fackConn := &connection.Connection{}
  // 遍歷 chan,執(zhí)行指令
  for p := range ch {
    if p.Err != nil {
      // 如果是 EOF,說(shuō)明文件讀取完畢
      if p.Err == io.EOF {
        break
      }
      logger.Error(err)
      continue
    }
    if p.Data == nil {
      logger.Error("empty payload")
      continue
    }
    // 將指令轉(zhuǎn)換成 MultiBulkReply 類型
    r, ok := p.Data.(*reply.MultiBulkReply)
    if !ok {
      logger.Error("exec multi mulk")
      continue
    }
    // 執(zhí)行指令
    rep := handler.database.Exec(fackConn, r.Args)
    if reply.IsErrReply(rep) {
      logger.Error(rep)
    }
  }
}

到此這篇關(guān)于使用go語(yǔ)言實(shí)現(xiàn)Redis持久化的示例代碼的文章就介紹到這了,更多相關(guān)go實(shí)現(xiàn)Redis持久化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go設(shè)計(jì)模式之模板方法模式講解和代碼示例

    Go設(shè)計(jì)模式之模板方法模式講解和代碼示例

    模版方法是一種行為設(shè)計(jì)模式, 它在基類中定義了一個(gè)算法的框架, 允許子類在不修改結(jié)構(gòu)的情況下重寫算法的特定步驟,本文將通過(guò)代碼示例給大家詳細(xì)的介紹一下Go模板方法模式,需要的朋友可以參考下
    2023-08-08
  • Go語(yǔ)言中的map擴(kuò)容機(jī)制

    Go語(yǔ)言中的map擴(kuò)容機(jī)制

    Go語(yǔ)言中的map是一種高效的數(shù)據(jù)結(jié)構(gòu),其擴(kuò)容機(jī)制確保了在大數(shù)據(jù)量情況下的性能,本文介紹了包括擴(kuò)容觸發(fā)條件、擴(kuò)容過(guò)程和漸進(jìn)式擴(kuò)容,感興趣的可以了解一下
    2024-12-12
  • Go語(yǔ)言結(jié)構(gòu)體標(biāo)簽用途、用法與注意事項(xiàng)示例詳解

    Go語(yǔ)言結(jié)構(gòu)體標(biāo)簽用途、用法與注意事項(xiàng)示例詳解

    結(jié)構(gòu)體是Go語(yǔ)言中一種重要的復(fù)合數(shù)據(jù)類型,它可以將多個(gè)不同類型的數(shù)據(jù)組合在一起,形成一個(gè)有意義的整體,這篇文章主要介紹了Go語(yǔ)言結(jié)構(gòu)體標(biāo)簽用途、用法與注意事項(xiàng)的相關(guān)資料,需要的朋友可以參考下
    2026-04-04
  • go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易比特幣系統(tǒng)之交易簽名及校驗(yàn)功能

    go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易比特幣系統(tǒng)之交易簽名及校驗(yàn)功能

    這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易比特幣系統(tǒng)之交易簽名及校驗(yàn)功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • 利用Go語(yǔ)言遍歷目錄下所有文件的示例代碼

    利用Go語(yǔ)言遍歷目錄下所有文件的示例代碼

    這篇文章主要介紹了如何使用 Go 語(yǔ)言遍歷指定目錄,遞歸地列出該目錄及其所有子目錄下的所有文件路徑,并有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下
    2025-07-07
  • go中new和make的區(qū)別小結(jié)

    go中new和make的區(qū)別小結(jié)

    new 只分配內(nèi)存,make 用于初始化 slice、map 和 channel,本文詳細(xì)的介紹了go中new和make的區(qū)別小結(jié),感興趣的可以了解一下
    2023-05-05
  • Go語(yǔ)言中的通道chan使用指南

    Go語(yǔ)言中的通道chan使用指南

    Go語(yǔ)言的通道chan是實(shí)現(xiàn)并發(fā)編程的關(guān)鍵工具,主要用于goroutine之間的數(shù)據(jù)傳輸,本文主要介紹了通道的基本操作如創(chuàng)建、發(fā)送、接收和關(guān)閉數(shù)據(jù),以及使用select語(yǔ)句進(jìn)行多路復(fù)用和超時(shí)控制,感興趣的可以了解一下
    2024-10-10
  • SingleFlight模式的Go并發(fā)編程學(xué)習(xí)

    SingleFlight模式的Go并發(fā)編程學(xué)習(xí)

    這篇文章主要為大家介紹了SingleFlight模式的Go并發(fā)編程學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • Golang sync.Pool的源碼解析

    Golang sync.Pool的源碼解析

    Pool是用于存放臨時(shí)對(duì)象的集合,這些對(duì)象是為了后續(xù)的使用,以達(dá)到復(fù)用對(duì)象的效果,本文將詳解解析sync.Pool 源碼,需要的朋友可以參考下
    2023-05-05
  • Go語(yǔ)言如何生成PDF文件實(shí)例探究

    Go語(yǔ)言如何生成PDF文件實(shí)例探究

    這篇文章主要為大家介紹了Go語(yǔ)言生成PDF文件的實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01

最新評(píng)論

驻马店市| 西乡县| 咸阳市| 项城市| 盐城市| 华池县| 东安县| 棋牌| 定兴县| 启东市| 荣昌县| 体育| 迁西县| 徐水县| 新巴尔虎左旗| 特克斯县| 弥勒县| 慈溪市| 武安市| 顺平县| 汤原县| 棋牌| 崇州市| 新泰市| 方正县| 屯门区| 新宁县| 瑞金市| 榆林市| 宁德市| 寻甸| 通海县| 大宁县| 漳浦县| 新密市| 阿克苏市| 文成县| 堆龙德庆县| 黄冈市| 平和县| 景德镇市|