💻 Exemplos: Capítulo 13: Integração com Redis para Cache de Alto Desempenho
Especialização em Backend com Go & Gin • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 01 — Cliente Redis e Operações Set/Get com TTL
package cache
import (
"context"
"encoding/json"
"time"
"github.com/redis/go-redis/v9"
)
var Ctx = context.Background()
func NovoClienteRedis() *redis.Client {
return redis.NewClient(&redis.Options{
Addr: "localhost:6379",
PoolSize: 10,
DialTimeout: 5 * time.Second,
ReadTimeout: 2 * time.Second,
WriteTimeout: 2 * time.Second,
})
}
// SalvarJSON serializa qualquer struct em JSON e grava com TTL.
func SalvarJSON(rdb *redis.Client, chave string, valor interface{}, ttl time.Duration) error {
bytes, err := json.Marshal(valor)
if err != nil {
return err
}
return rdb.Set(Ctx, chave, bytes, ttl).Err()
}
// ObterJSON busca a chave e desserializa no destino apontado por dest.
// Retorna redis.Nil quando a chave não existe (cache miss).
func ObterJSON(rdb *redis.Client, chave string, dest interface{}) error {
val, err := rdb.Get(Ctx, chave).Result()
if err != nil {
return err
}
return json.Unmarshal([]byte(val), dest)
}
💻 Código de Exemplo 02 — Middleware de Cache de Resposta HTTP (Cache-Aside)
package middlewares
import (
"errors"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9"
)
// bodyCapturer intercepta o corpo escrito pelo handler para poder gravá-lo
// no cache depois que a rota real for executada (em caso de miss).
type bodyCapturer struct {
gin.ResponseWriter
corpo []byte
}
func (b *bodyCapturer) Write(dados []byte) (int, error) {
b.corpo = append(b.corpo, dados...)
return b.ResponseWriter.Write(dados)
}
// CacheDeResposta serve GETs diretamente do Redis quando a chave (o path)
// já está cacheada; em cache miss, deixa o handler real responder e grava
// o resultado no Redis com TTL curto antes de retornar.
func CacheDeResposta(rdb *redis.Client, ttl time.Duration) gin.HandlerFunc {
ctx := Ctx
return func(c *gin.Context) {
if c.Request.Method != http.MethodGet {
c.Next()
return
}
chave := "http-cache:" + c.Request.URL.String()
if val, err := rdb.Get(ctx, chave).Result(); err == nil {
c.Data(http.StatusOK, "application/json", []byte(val))
c.Abort()
return
} else if !errors.Is(err, redis.Nil) {
c.Next() // Redis indisponível: degrada sem cache em vez de falhar a request
return
}
writer := &bodyCapturer{ResponseWriter: c.Writer}
c.Writer = writer
c.Next()
if c.Writer.Status() == http.StatusOK {
rdb.Set(ctx, chave, writer.corpo, ttl)
}
}
}
💻 Código de Exemplo 03 — Invalidação por Padrão (SCAN + DEL em Lote)
package cache
import "github.com/redis/go-redis/v9"
// PurgarPorPadrao remove todas as chaves que casam com um padrão glob
// (ex.: "produtos:*") usando SCAN em vez de KEYS, que evita bloquear o
// servidor Redis inteiro em keyspaces grandes (KEYS é O(N) e não paginado).
func PurgarPorPadrao(rdb *redis.Client, padrao string) (int64, error) {
var removidas int64
iter := rdb.Scan(Ctx, 0, padrao, 100).Iterator()
var lote []string
for iter.Next(Ctx) {
lote = append(lote, iter.Val())
if len(lote) >= 100 {
n, err := rdb.Del(Ctx, lote...).Result()
if err != nil {
return removidas, err
}
removidas += n
lote = lote[:0]
}
}
if err := iter.Err(); err != nil {
return removidas, err
}
if len(lote) > 0 {
n, err := rdb.Del(Ctx, lote...).Result()
if err != nil {
return removidas, err
}
removidas += n
}
return removidas, nil
}