據(jù)庫優(yōu)化完全指南:用一層緩存加一套讀寫分離把詳情接口壓回毫秒級)
go-zero數(shù)據(jù)庫優(yōu)化完全指南用一層緩存加一套讀寫分離把詳情接口壓回毫秒級【免費下載鏈接】go-zeroA cloud-native Go microservices framework with cli tool for productivity.項目地址: https://gitcode.com/GitHub_Trending/go/go-zero凌晨兩點又收到慢查詢告警用戶詳情接口 3 秒才超時。go-zero數(shù)據(jù)庫優(yōu)化其實只需要做兩件事讀路徑前面墊一層緩存寫路徑之外掛兩個從庫——詳情接口 RT 從秒級回落到毫秒級主庫 QPS 降掉約九成。本文給出完整配置與代碼。讓高頻點查不再壓垮主庫結(jié)論先行主鍵點查這類流量先走 cache.Take命中即返回未命中才落庫并回寫緩存。?原理不復(fù)雜緩存組件用一致性哈希把同一個 key 固定路由到同一個 Redis 節(jié)點所以同一 key 的讀、寫、刪永遠(yuǎn)落在同一處不需要自己寫分片邏輯。每個節(jié)點還掛了一個 SingleFlight 屏障熱點 key 過期瞬間的并發(fā)請求只有一個真正打到數(shù)據(jù)庫其余人共享它的結(jié)果擊穿問題由此解決。默認(rèn) TTL 是 7 天空結(jié)果占位符只保留 1 分鐘需要調(diào)整時用 WithExpiry、WithNotFoundExpiry 兩個 Option 即可入口在 core/stores/cache/cache.go。緩存節(jié)點YAML怎么配Cache: - Host: 10.0.0.1:6379 Type: node Weight: 100 - Host: 10.0.0.2:6379 Type: node Weight: 100Weight 決定哈希權(quán)重擴(kuò)容節(jié)點時改這里就能生效不用動代碼。緩存鍵前綴怎么設(shè)計goctl 生成的鍵是「cache#表名#主鍵類型#」三段式比如 cache#student#id#12。自己手寫時照這個約定來前綴加主鍵不同索引不共享同一個鍵這樣更新時只按主鍵刪成本恒定也不會誤傷別的查詢。Take查詢代碼怎么寫key : fmt.Sprintf(cache#student#id#%d, id) var resp Student err : m.cache.TakeCtx(ctx, resp, key, func(v any) error { // 緩存未命中回查數(shù)據(jù)庫序列化與回寫由組件代勞 return m.conn.QueryRowCtx(ctx, v, select id,name from student where id ?, id) })回調(diào)只管查庫命中統(tǒng)計、寫緩存全部是組件內(nèi)部行為。某條查詢想單獨指定 TTL換成 TakeWithExpireCtx 即可。穿透與雪崩的坑組件已經(jīng)替你堵了go-zero緩存穿透解決方案不用手寫代碼查庫為空時組件會寫一個占位值TTL 默認(rèn) 1 分鐘窗口期內(nèi)的重復(fù)請求直接命中緩存。而 go-zero緩存雪崩規(guī)避策略藏在同一份源碼里——實際 TTL 會在配置值的 [0.95, 1.05] 區(qū)間隨機(jī)抖動避免大批 key 同一秒集體過期。這兩處邏輯都在 core/stores/cache/cachenode.go 里搜 notFoundExpiry 和 unstableExpiry 就能看到。讀流量超過八成時怎么拆離主庫結(jié)論先行g(shù)o-zero讀寫分離配置只占 3 行 YAMLgo-zero寫后讀路由則靠 3 個 With 函數(shù)控制。有個默認(rèn)行為必須知道上下文里什么都沒標(biāo)時讀寫全部走主庫。從庫流量是「按需接入」的這個保守設(shè)計能避免新人把讀流量誤路由到從庫上。路由判斷就一行——只要模式不是 readReplica一律視為主庫見 core/stores/sqlx/rwstrategy.go。主從YAML怎么配DataSource: - root:123456tcp(10.0.0.1:3306)/test Replicas: - root:123456tcp(10.0.0.2:3306)/test - root:123456tcp(10.0.0.3:3306)/test Policy: random三個字段一一對應(yīng) core/stores/sqlx/config.go 里的 SqlConf 結(jié)構(gòu)主庫 DSN、從庫 DSN 列表、策略round-robin 或 random不填默認(rèn)輪詢。從庫只有一臺時兩種策略無差別從庫越多輪詢的均勻性越有價值主從延遲大時不標(biāo)模式讓流量整體回主庫是最快的降級手段。寫后讀怎么路由// 寫后立即讀必須命中主庫規(guī)避復(fù)制延遲導(dǎo)致的舊值 ctx : sqlx.WithReadPrimary(context.Background()) user, err : userModel.FindOne(ctx, id) // 對一致性不敏感的列表讀走從庫 ctx sqlx.WithReadReplica(context.Background()) users, err : userModel.FindAll(ctx)判斷標(biāo)準(zhǔn)就一條用戶「剛寫的東西必須馬上看得到」就用 WithReadPrimary否則一律丟給從庫。寫成功之后別讓接口讀到舊值結(jié)論先行寫成功之后刪緩存讓下一次讀自然重建比直接寫新值更穩(wěn)。?if _, err : userModel.Update(ctx, req); err ! nil { return err } // 按主鍵刪舊值下次讀走 Take 重建緩存 key : fmt.Sprintf(cache#user#id#%d, req.Id) return cache.DelCtx(ctx, key)為什么刪而不是 Set 新值刪除沒有并發(fā)寫互相覆蓋緩存的窗口且重建路徑與首次填充是同一條序列化鏈路不存在雙寫臟數(shù)據(jù)。刪緩存失敗也不用慌組件內(nèi)部有異步重試任務(wù)兜底最壞情況是緩存舊值存活一個 TTL。和上一節(jié)串起來寫主庫、刪緩存、寫后讀加 WithReadPrimary三步銜接完就沒有舊值縫隙。上線前檢查清單緩存節(jié)點 Weight 總和大于 0鍵前綴與別的服務(wù)無沖突Replicas 確實連的是從庫Policy 按讀流量分布選 round-robin 或 random「寫后立即讀」的場景都掛了 WithReadPrimary寫成功路徑都按主鍵刪了對應(yīng)緩存模型用 goctl 生成時帶上 -c 開關(guān)緩存邏輯開箱即用開關(guān)定義在 tools/goctl/model/sql/command/command.gogoctl model mysql ddl -src user.ddl -dir model -c這份配置路徑如果幫你攔下了慢查詢告警點個贊、收藏備用關(guān)注我下一篇拆 go-zero 的 Redis 與熔斷限流。【免費下載鏈接】go-zeroA cloud-native Go microservices framework with cli tool for productivity.項目地址: https://gitcode.com/GitHub_Trending/go/go-zero創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考