0

Bài 13: Monitoring & Best Practices - Giám sát hệ thống và Anti-patterns

Mục tiêu bài học:

  • Cấu hình giám sát (Observability) và theo dõi các chỉ số quan trọng của go-redis.
  • Nắm vững cách bắt log các truy vấn chậm (Slow Queries) tự động.
  • Nhận diện và phòng tránh các Anti-patterns (sai lầm phổ biến) có thể đánh sập hệ thống Redis trên môi trường Production.

1. Giám Sát Hệ Thống (Monitoring) Với Hook

Trong môi trường Production, bạn không thể cứ "đoán" xem Redis đang chạy nhanh hay chậm. Thư viện go-redis (từ v9) cung cấp cơ chế Hook cho phép bạn can thiệp vào vòng đời của mọi câu lệnh gửi đi. Đây là nơi hoàn hảo để gắn OpenTelemetry (tracing), Prometheus (metrics), hoặc đơn giản là ghi log các truy vấn chậm.

Code mẫu: Viết Custom Hook bắt Slow Query

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/redis/go-redis/v9"
)

// Khai báo một struct implement interface redis.Hook
type slowQueryHook struct{}

func (slowQueryHook) DialHook(next redis.DialHook) redis.DialHook {
	return next
}

func (slowQueryHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
	return next
}

// Can thiệp vào từng lệnh đơn lẻ
func (slowQueryHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
	return func(ctx context.Context, cmd redis.Cmder) error {
		start := time.Now()
		
		// Thực thi lệnh thực tế
		err := next(ctx, cmd)
		
		// Tính toán thời gian phản hồi
		duration := time.Since(start)
		
		// Cảnh báo nếu lệnh mất hơn 50ms (ngưỡng tùy chỉnh)
		if duration > 50*time.Millisecond {
			fmt.Printf("[CẢNH BÁO] Truy vấn chậm: Lệnh '%s' mất %v để xử lý!\n", cmd.Name(), duration)
		}
		
		return err
	}
}

func main() {
	rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
	
	// Gắn Hook vào Client
	rdb.AddHook(slowQueryHook{})

	// Giả lập một lệnh chặn (Block) gây chậm để test hook
	ctx := context.Background()
	rdb.BRPop(ctx, 1*time.Second, "empty_queue") 
}

Mẹo Production: Nếu công ty bạn dùng hệ sinh thái Observability hiện đại, hãy cài trực tiếp package github.com/redis/go-redis/extra/redisotel/v9 để tự động đẩy metrics và Distributed Tracing lên Jaeger/Grafana mà không cần tự viết Hook.


2. Các Chỉ Số Sinh Tử (Vital Metrics) Cần Theo Dõi

Ngoài việc theo dõi từ phía Go (rdb.PoolStats()), bạn phải luôn thiết lập cảnh báo (Alert) trên chính server Redis cho 4 chỉ số sau:

Tên Metric Ý nghĩa & Dấu hiệu nguy hiểm
Used Memory Biểu đồ liên tục đi lên không có điểm dừng là dấu hiệu rò rỉ dữ liệu (quên set TTL). Cần giữ bộ nhớ dưới 80% RAM thực tế.
Evicted Keys Số lượng key bị Redis "âm thầm" xóa đi vì hết RAM. Nếu chỉ số này cao đột biến, Cache Hit Ratio của bạn sẽ tụt thê thảm.
Connected Clients Nếu số lượng kết nối tăng không ngừng, ứng dụng Go của bạn đang bị Goroutine Leaks hoặc cấu hình Pool bị sai.
Command Stats Nhận diện các lệnh chiếm nhiều CPU nhất (ví dụ: KEYS, SMEMBERS trên tập lớn).

3. Những Anti-Patterns "Chết Người" Cần Tránh

Sai lầm 1: Dùng lệnh KEYS * trên Production

  • Nguyên nhân: Redis là đơn luồng (Single-threaded). Khi bạn gọi KEYS user:* trong một DB có 10 triệu records, Redis sẽ đóng băng toàn bộ server để dò tìm hàng trăm ngàn key khớp chuỗi. Trong vài giây đó, mọi request khác đều bị time-out.
  • Giải pháp: Sử dụng lệnh SCAN. SCAN trả về kết quả theo từng trang (cursor) nhỏ, cho phép Redis xen kẽ xử lý các lệnh khác, không gây nghẽn hệ thống.

Sai lầm 2: Tạo ra "Big Keys" (Key khổng lồ)

  • Nguyên nhân: Lưu một chuỗi JSON nặng 5MB hoặc một tập hợp Hash/Set chứa 100,000 phần tử vào chung một Key. Vì Redis phải cấp phát/giải phóng bộ nhớ liền mạch, việc đọc/ghi các "Big Keys" này gây ra độ trễ mạng khổng lồ và làm treo CPU.
  • Giải pháp: Cắt nhỏ dữ liệu (Chunking). Nếu là Hash, phân tán ra nhiều key nhỏ (ví dụ: hash:1, hash:2). Hạn chế kích thước Value dưới 1MB (lý tưởng là dưới 10KB).

Sai lầm 3: Coi Redis là Database chính mà không có chiến lược Eviction

  • Nguyên nhân: Lưu dữ liệu vào Redis vĩnh viễn (TTL = 0) dẫn đến cạn kiệt RAM. Khi RAM đầy, Redis sẽ báo lỗi OOM command not allowed và từ chối mọi lệnh Ghi mới, kéo theo ứng dụng sập.
  • Giải pháp:
    • Luôn set TTL cho các dữ liệu mang tính thời vụ (Session, Cache API).
    • Cấu hình maxmemory-policy trong file redis.conf thành allkeys-lru hoặc volatile-lru để Redis tự động dọn dẹp các key ít dùng nhất khi bộ nhớ chạm đỉnh.

Sai lầm 4: Bỏ qua Context Cancellation

  • Nguyên nhân: Trong Go, nếu client đột ngột đóng trình duyệt (hủy request API), request đó vẫn ngầm chạy và ngốn tài nguyên kết nối Redis nếu bạn truyền vào một context.Background() cố định.
  • Giải pháp: Luôn sử dụng context truyền xuống từ HTTP Request (r.Context()). Khi request bị hủy bỏ, context sẽ trigger tín hiệu Hủy, giúp go-redis lập tức ngắt lệnh và thu hồi kết nối trả về Pool.

🏁 Tổng Kết Series

Chúc mừng bạn đã hoàn thành chặng đường làm chủ go-redis. Từ những bước thiết lập đơn giản đầu tiên, cho đến kiến trúc High Availability (Cluster/Sentinel), và các cấu trúc dữ liệu mạnh mẽ như Streams hay HyperLogLog.

Redis không chỉ là một kho lưu trữ Key-Value đơn thuần. Khi kết hợp với sức mạnh đồng thời của Go, bạn đã nắm trong tay công thức để xây dựng các hệ thống Queue, Real-time Analytics, và Distributed Locks chịu tải hàng chục ngàn truy vấn mỗi giây. Hãy áp dụng đúng các Best Practices, và hệ thống của bạn sẽ sẵn sàng cho mọi môi trường Production!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí