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