iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Software Development

從零開始打造 Redis:以 Go 建立 Production Ready 應用程式 系列 第 13

[Day 13] 資料持久化(上):AOF 的實現

  • 分享至 

  • xImage
  •  

Redis 作為一個資料庫,其最重要的特性就是_ Data Persistence(資料持久化)_:當用戶儲存了一個值之後,在下次重新啟動服務這個值也應該是有效的。因此,我們不能僅僅將資料存在記憶體中,它應該要被寫入硬碟。

Redis 的做法

Redis 有兩種資料持久化策略:

  • Append Only File(AOF):用 Log 的方式記錄所有寫入的命令
  • Redis Database(RDB):將當前記憶體的狀態用 snapshot 的方式記錄下來

我們在這邊先實現 AOF 的方案,因為它比較容易實作。

註:RDB 的部份其實滿困難的,因為 RDB 有很多特性是其於 Redis 內部的記憶體排佈方式,這與 Go 的記憶體排佈方式有著巨大差異,雖然可以硬性支援但在效能上與易用性上會與官方版本存在巨大落差。

AOF

結構

其實 AOF 就是由一大堆的 RESP Value 經過 Marshal() 後的產物:

$ redis-cli SET foo bar
OK
$ cat database.aof
*3
$3
set
$3
foo
$3
bar

因此,實作上這個邏輯很簡單:只要 storage 成功儲存資料,就把 RESP 原封不動地塞進 AOF 即可。

讀取與寫入

可以在 ./internal/service 底下建立 AOF 相關的介面,這邊一樣使用介面是因為未來也可以抽換成其它的 AOF 實作,例如像是 ledisdb/ledisdb 中就整合了許多不同的存儲後端。

// file: ./internal/service/aof.go

type AOF interface {
	Read() (*resp.Command, error)
	Write(*resp.Command) error
	Close() error
}

並且用 os.(*File) 實現:

type file struct {
	f  *os.File
	rd *resp.Reader
	mu sync.Mutex
}

func (aof *file) Read() (*resp.Command, error) {
	return resp.ReadCommand(aof.rd)
}

func (aof *file) Write(v *resp.Command) error {
	aof.mu.Lock()
	defer aof.mu.Unlock()

	if !v.Dirty() {
		return nil
	}

	marshaled := v.Marshal()
	n, err := aof.f.Write(marshaled)
	if err != nil {
		return err
	}
	if n != len(marshaled) {
		return fmt.Errorf("wrote length mismatch: got %d want %d", n, len(marshaled))
	}

	return nil
}

func (aof *file) Close() error {
	if aof.f == nil {
		return nil
	}

	aof.mu.Lock()
	defer aof.mu.Unlock()

	return aof.f.Close()
}

為了判斷指令是不是應該被寫入 AOF(只有會造成資源變動的指令才應該被寫入 AOF,例如像 SET),因此我在 resp.(*Command) 加入 IsDirty() 來判斷它是不是 SET 指令。


上一篇
[Day 12] Redis 指令實作:GET 與 SET
下一篇
[Day 14] 資料持久化(中): Middleware
系列文
從零開始打造 Redis:以 Go 建立 Production Ready 應用程式 15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言