iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0

在每次 server.(*simpleSrv).serve() 執行完之後都會呼叫 defer conn.Close(),所以每次都必須重新連線才能執行 Redis 指令。

從以下的 redis-cli 指令就可以很明顯地感覺到異狀:

$ redis-cli -p 16379
127.0.0.1:16379> ping
Error: Server closed the connection
not connected> ping
OK
127.0.0.1:16379> ping
Error: Server closed the connection

TCP 連線需要經過握手,這個過程對於系統資源是有極大消耗的,因此現代的 Server-Client 架構中都會盡量重複使用一個 TCP 連線(例如 HTTP 有 Keep-Alive),而在 Redis 中將這個稱為 Redis pipelining

連線複用

要加入這個特性也很容易:只要把 server.(*simpleSrv).serve() 變成一個無限迴圈即可,並且在遇到錯誤時中止。

註:如果試圖從已經關閉的 net.Conn 讀取資料或寫入資料會產生錯誤,因此可以利用這個特性跳出迴圈。

// file: internal/server/server.go
func (s *simpleSrv) serve(conn net.Conn) {
	defer conn.Close()

	rd := resp.NewReader(conn)

	for {
		ret, err := s.handler.ServeRESP(context.Background(), rd)
		if err != nil {
			return
		}

		if _, err := conn.Write(ret.Marshal()); err != nil {
			slog.Error("failed to write to conn:", slog.Any("error", err))
			return
		}
	}

}

錯誤處理

然而,引入連線複用的機制之後卻也衍生了另一個問題:如果客戶端發生錯誤(例如輸入錯誤的指令或參數),連線也一樣會被關閉:這並不是我們預期的。

因此,我們可以將 server.(Handler).ServeRESP() 所回傳的 err 分為兩類:

  • 客戶端錯誤:未知命令、參數數量不正確--不會關閉連線,用戶可以繼續使用同一條 TCP 連線
  • 伺服端錯誤:連線失敗、記憶體用盡--無法提供服務,應該直接關閉連線

註:其實這個思想跟 HTTP 4xx 與 5xx 的狀態碼是類似的,只不過 HTTP 的複雜度高得多。

定義 ErrClientErrServer,並且在讀取 Redis 指令回傳 resp.ErrProtocol 的時候,視為客戶端錯誤、其餘則是伺服端錯誤:

// file: ./internal/server/handler.go
var (
	ErrClient = errors.New("client error")
	ErrServer = errors.New("server error")
)

// ...

func (h *simpleHandler) ServeRESP(ctx context.Context, rd *resp.Reader) (resp.Value, error) {
	cmd, err := resp.ReadCommand(rd)
	if err != nil {
		if errors.Is(err, resp.ErrProtocol) {
			return resp.NewSimpleError(err), fmt.Errorf("%w: %w", ErrClient, err)
		} else {
			return nil, fmt.Errorf("%w: %w", ErrServer, err)
		}
	}

	slog.Info("Read RESP command:", slog.Any("command", cmd))

	return h.exec(ctx, cmd)
}

上一篇
[Day 09] RESP(下):Redis 的通訊協定--指令
下一篇
[Day 11] Redis 指令與 PING 實作
系列文
從零開始打造 Redis:以 Go 建立 Production Ready 應用程式11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言