---
title: "Go e Redis: Cache e Session Store Completo"
url: "https://golang.com.br/tutoriais/go-redis-cache/"
markdown_url: "https://golang.com.br/tutoriais/go-redis-cache.MD"
description: "Aprenda a usar Redis com Go para caching de alta performance, gerenciamento de sessões e arquiteturas escaláveis. Guia completo com exemplos práticos."
date: "2026-02-10"
author: ""
---

# Go e Redis: Cache e Session Store Completo

Aprenda a usar Redis com Go para caching de alta performance, gerenciamento de sessões e arquiteturas escaláveis. Guia completo com exemplos práticos.


# Go e Redis: Cache e Session Store Completo

O Redis é um dos bancos de dados em memória mais populares do mundo, e quando combinado com Go, cria aplicações extremamente rápidas e escaláveis. Neste guia completo, você vai aprender a implementar **caching de alta performance** e **gerenciamento de sessões** usando Go e Redis.

## Por Que Usar Redis com Go?

Antes de mergulhar no código, entenda por que essa combinação é tão poderosa:

| Recurso | Benefício |
|---------|-----------|
| **Latência** | Sub-milissegundo para operações simples |
| **Throughput** | 100.000+ operações por segundo |
| **Estruturas** | Strings, Hashes, Lists, Sets, Sorted Sets |
| **Persistência** | Opcional (RDB snapshots, AOF logs) |
| **Pub/Sub** | Mensageria em tempo real |
| **Go Driver** | go-redis é maduro e bem mantido |

> **Caso Real:** Empresas como Twitter, GitHub e Stack Overflow usam Redis para caching, reduzindo drasticamente a carga em seus bancos de dados principais.

## Configurando o Redis Client em Go

### Instalação

```bash
go get github.com/redis/go-redis/v9
```

### Conexão Básica

```go
package main

import (
    "context"
    "fmt"
    "log"
    "time"

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

var ctx = context.Background()

func main() {
    // Configuração do cliente Redis
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // sem senha
        DB:       0,  // banco de dados padrão
        PoolSize: 10, // tamanho do pool de conexões
    })

    // Testa a conexão
    pong, err := rdb.Ping(ctx).Result()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Conectado:", pong)

    defer rdb.Close()
}
```

### Configuração para Produção

```go
rdb := redis.NewClient(&redis.Options{
    Addr:         "redis.example.com:6379",
    Password:     os.Getenv("REDIS_PASSWORD"),
    DB:           0,
    MaxRetries:   3,
    DialTimeout:  5 * time.Second,
    ReadTimeout:  3 * time.Second,
    WriteTimeout: 3 * time.Second,
    PoolSize:     20,
    MinIdleConns: 5,
})
```

## Padrões de Caching em Go

### 1. Cache-Aside (Lazy Loading)

O padrão mais comum: verifica o cache primeiro, busca no banco se não existir.

```go
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func getUser(ctx context.Context, rdb *redis.Client, userID int) (*User, error) {
    cacheKey := fmt.Sprintf("user:%d", userID)
    
    // 1. Tenta buscar do cache
    cached, err := rdb.Get(ctx, cacheKey).Result()
    if err == nil {
        var user User
        if err := json.Unmarshal([]byte(cached), &user); err == nil {
            fmt.Println("Cache hit!")
            return &user, nil
        }
    }
    
    // 2. Cache miss - busca do banco de dados
    user, err := fetchUserFromDB(userID)
    if err != nil {
        return nil, err
    }
    
    // 3. Armazena no cache para próximas requisições
    userJSON, _ := json.Marshal(user)
    err = rdb.Set(ctx, cacheKey, userJSON, 5*time.Minute).Err()
    if err != nil {
        log.Printf("Erro ao salvar no cache: %v", err)
    }
    
    return user, nil
}
```

### 2. Write-Through Cache

Atualiza o cache simultaneamente com o banco de dados.

```go
func updateUser(ctx context.Context, rdb *redis.Client, user *User) error {
    // 1. Atualiza o banco de dados
    if err := updateUserInDB(user); err != nil {
        return err
    }
    
    // 2. Atualiza o cache
    cacheKey := fmt.Sprintf("user:%d", user.ID)
    userJSON, _ := json.Marshal(user)
    
    if err := rdb.Set(ctx, cacheKey, userJSON, 5*time.Minute).Err(); err != nil {
        log.Printf("Erro ao atualizar cache: %v", err)
        // Não retorna erro - cache pode ser reconstruído
    }
    
    return nil
}
```

### 3. Cache com TTL (Time To Live)

Define expiração automática para evitar dados desatualizados.

```go
// Cache com diferentes TTLs baseado no tipo de dado
func setWithTTL(ctx context.Context, rdb *redis.Client, key string, value interface{}, ttl time.Duration) error {
    data, err := json.Marshal(value)
    if err != nil {
        return err
    }
    return rdb.Set(ctx, key, data, ttl).Err()
}

// Uso
setWithTTL(ctx, rdb, "config:app", config, 1*time.Hour)      // Configurações: 1h
setWithTTL(ctx, rdb, "user:123", user, 15*time.Minute)       // Usuários: 15min
setWithTTL(ctx, rdb, "session:abc", session, 24*time.Hour)   // Sessões: 24h
```

## Gerenciamento de Sessões com Redis

### Estrutura de Sessão

```go
type Session struct {
    UserID    int       `json:"user_id"`
    Username  string    `json:"username"`
    Data      map[string]interface{} `json:"data"`
    CreatedAt time.Time `json:"created_at"`
}

type SessionManager struct {
    rdb       *redis.Client
    ctx       context.Context
    ttl       time.Duration
}

func NewSessionManager(rdb *redis.Client, ttl time.Duration) *SessionManager {
    return &SessionManager{
        rdb: rdb,
        ctx: context.Background(),
        ttl: ttl,
    }
}
```

### Criar Sessão

```go
func (sm *SessionManager) CreateSession(userID int, username string) (string, error) {
    sessionID := generateSessionID() // UUID ou token seguro
    
    session := Session{
        UserID:    userID,
        Username:  username,
        Data:      make(map[string]interface{}),
        CreatedAt: time.Now(),
    }
    
    sessionJSON, err := json.Marshal(session)
    if err != nil {
        return "", err
    }
    
    key := fmt.Sprintf("session:%s", sessionID)
    if err := sm.rdb.Set(sm.ctx, key, sessionJSON, sm.ttl).Err(); err != nil {
        return "", err
    }
    
    // Mantém índice de sessões por usuário
    userSessionsKey := fmt.Sprintf("user:sessions:%d", userID)
    sm.rdb.SAdd(sm.ctx, userSessionsKey, sessionID)
    sm.rdb.Expire(sm.ctx, userSessionsKey, sm.ttl)
    
    return sessionID, nil
}
```

### Validar e Recuperar Sessão

```go
func (sm *SessionManager) GetSession(sessionID string) (*Session, error) {
    key := fmt.Sprintf("session:%s", sessionID)
    
    data, err := sm.rdb.Get(sm.ctx, key).Result()
    if err == redis.Nil {
        return nil, fmt.Errorf("sessão não encontrada ou expirada")
    }
    if err != nil {
        return nil, err
    }
    
    var session Session
    if err := json.Unmarshal([]byte(data), &session); err != nil {
        return nil, err
    }
    
    // Renova TTL (sliding expiration)
    sm.rdb.Expire(sm.ctx, key, sm.ttl)
    
    return &session, nil
}

func (sm *SessionManager) DestroySession(sessionID string) error {
    session, err := sm.GetSession(sessionID)
    if err != nil {
        return err
    }
    
    // Remove sessão principal
    key := fmt.Sprintf("session:%s", sessionID)
    if err := sm.rdb.Del(sm.ctx, key).Err(); err != nil {
        return err
    }
    
    // Remove do índice do usuário
    userSessionsKey := fmt.Sprintf("user:sessions:%d", session.UserID)
    sm.rdb.SRem(sm.ctx, userSessionsKey, sessionID)
    
    return nil
}
```

### Middleware de Autenticação (Gin/Gorilla)

```go
func AuthMiddleware(sm *SessionManager) gin.HandlerFunc {
    return func(c *gin.Context) {
        sessionID, err := c.Cookie("session_id")
        if err != nil {
            c.JSON(401, gin.H{"error": "não autenticado"})
            c.Abort()
            return
        }
        
        session, err := sm.GetSession(sessionID)
        if err != nil {
            c.JSON(401, gin.H{"error": "sessão inválida"})
            c.Abort()
            return
        }
        
        // Disponibiliza dados da sessão no contexto
        c.Set("user_id", session.UserID)
        c.Set("username", session.Username)
        c.Set("session", session)
        
        c.Next()
    }
}
```

## Pub/Sub com Redis em Go

Implemente comunicação em tempo real entre serviços.

### Publisher

```go
func publishMessage(rdb *redis.Client, channel string, message string) error {
    return rdb.Publish(ctx, channel, message).Err()
}

// Uso
publishMessage(rdb, "notifications", `{"type":"new_order","order_id":123}`)
```

### Subscriber

```go
func subscribeToChannel(rdb *redis.Client, channel string) {
    pubsub := rdb.Subscribe(ctx, channel)
    defer pubsub.Close()
    
    // Verifica conexão
    if _, err := pubsub.Receive(ctx); err != nil {
        log.Fatal(err)
    }
    
    // Canal para mensagens
    ch := pubsub.Channel()
    
    fmt.Printf("Inscrito no canal: %s\n", channel)
    
    for msg := range ch {
        fmt.Printf("Canal: %s | Mensagem: %s\n", msg.Channel, msg.Payload)
        
        // Processa mensagem
        processMessage(msg.Payload)
    }
}

func processMessage(payload string) {
    var notification map[string]interface{}
    if err := json.Unmarshal([]byte(payload), &notification); err != nil {
        log.Printf("Erro ao parsear mensagem: %v", err)
        return
    }
    
    // Lógica de processamento
    fmt.Printf("Processando notificação: %+v\n", notification)
}
```

### Pattern Matching em Pub/Sub

```go
// Inscreve em múltiplos canais com padrão
pubsub := rdb.PSubscribe(ctx, "user:*:notifications")

ch := pubsub.Channel()
for msg := range ch {
    // Recebe mensagens de user:123:notifications, user:456:notifications, etc.
    fmt.Printf("Pattern: %s | Channel: %s\n", msg.Pattern, msg.Channel)
}
```

## Otimização de Performance

### 1. Pipeline para Operações em Lote

Reduzida round-trips de rede:

```go
func batchOperations(rdb *redis.Client) error {
    pipe := rdb.Pipeline()
    
    // Enfileira operações
    pipe.Set(ctx, "key1", "value1", 0)
    pipe.Set(ctx, "key2", "value2", 0)
    pipe.Get(ctx, "key1")
    pipe.Incr(ctx, "counter")
    
    // Executa todas de uma vez
    cmders, err := pipe.Exec(ctx)
    if err != nil {
        return err
    }
    
    // Processa resultados
    for _, cmder := range cmders {
        fmt.Println(cmder)
    }
    
    return nil
}
```

### 2. Transactions (MULTI/EXEC)

```go
func transactionalUpdate(rdb *redis.Client, userID int, newBalance float64) error {
    key := fmt.Sprintf("balance:%d", userID)
    
    err := rdb.Watch(ctx, func(tx *redis.Tx) error {
        // Verifica saldo atual
        currentBalance, err := tx.Get(ctx, key).Float64()
        if err != nil && err != redis.Nil {
            return err
        }
        
        if newBalance < 0 && currentBalance < -newBalance {
            return fmt.Errorf("saldo insuficiente")
        }
        
        // Executa transação
        _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
            pipe.Set(ctx, key, currentBalance+newBalance, 0)
            pipe.Incr(ctx, "transactions:count")
            return nil
        })
        
        return err
    }, key)
    
    return err
}
```

### 3. Connection Pooling

O go-redis gerencia automaticamente, mas você pode ajustar:

```go
rdb := redis.NewClient(&redis.Options{
    PoolSize:     30,              // Conexões máximas
    MinIdleConns: 10,              // Conexões ociosas mínimas
    MaxConnAge:   time.Hour,       // Tempo máximo de vida da conexão
    PoolTimeout:  5 * time.Second, // Timeout para obter conexão do pool
    IdleTimeout:  10 * time.Minute,// Timeout para conexões ociosas
})
```

### 4. Compressão de Dados

Para objetos grandes, use compressão:

```go
import "github.com/klauspost/compress/zstd"

func setCompressed(ctx context.Context, rdb *redis.Client, key string, data interface{}) error {
    jsonData, err := json.Marshal(data)
    if err != nil {
        return err
    }
    
    // Comprime com zstd
    encoder, _ := zstd.NewWriter(nil)
    compressed := encoder.EncodeAll(jsonData, make([]byte, 0, len(jsonData)))
    
    return rdb.Set(ctx, key, compressed, 0).Err()
}
```

## Exemplo Completo: API com Caching

```go
package main

import (
    "context"
    "encoding/json"
    "log"
    "net/http"
    "time"

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

type Product struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

type CachedRepository struct {
    rdb *redis.Client
    ctx context.Context
}

func NewCachedRepository(rdb *redis.Client) *CachedRepository {
    return &CachedRepository{
        rdb: rdb,
        ctx: context.Background(),
    }
}

func (r *CachedRepository) GetProduct(id int) (*Product, error) {
    cacheKey := fmt.Sprintf("product:%d", id)
    
    // Tenta cache
    cached, err := r.rdb.Get(r.ctx, cacheKey).Result()
    if err == nil {
        var p Product
        json.Unmarshal([]byte(cached), &p)
        return &p, nil
    }
    
    // Simula busca no banco
    product := &Product{
        ID:    id,
        Name:  fmt.Sprintf("Produto %d", id),
        Price: float64(id) * 10.99,
    }
    
    // Salva no cache
    data, _ := json.Marshal(product)
    r.rdb.Set(r.ctx, cacheKey, data, 10*time.Minute)
    
    return product, nil
}

func (r *CachedRepository) InvalidateProduct(id int) {
    cacheKey := fmt.Sprintf("product:%d", id)
    r.rdb.Del(r.ctx, cacheKey)
}

func main() {
    // Redis client
    rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    defer rdb.Close()
    
    repo := NewCachedRepository(rdb)
    
    // API
    r := gin.Default()
    
    r.GET("/products/:id", func(c *gin.Context) {
        id, _ := strconv.Atoi(c.Param("id"))
        product, err := repo.GetProduct(id)
        if err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        c.JSON(200, product)
    })
    
    r.PUT("/products/:id", func(c *gin.Context) {
        id, _ := strconv.Atoi(c.Param("id"))
        // Atualiza no banco...
        
        // Invalida cache
        repo.InvalidateProduct(id)
        
        c.JSON(200, gin.H{"message": "atualizado"})
    })
    
    log.Println("Server running on :8080")
    r.Run(":8080")
}
```

## Monitoramento e Health Checks

```go
func healthCheck(rdb *redis.Client) map[string]interface{} {
    stats := make(map[string]interface{})
    
    // Info do servidor
    info := rdb.Info(ctx, "server")
    stats["server_info"] = info.Val()
    
    // Estatísticas de memória
    memInfo := rdb.Info(ctx, "memory")
    stats["memory"] = memInfo.Val()
    
    // Conexões ativas
    poolStats := rdb.PoolStats()
    stats["hits"] = poolStats.Hits
    stats["misses"] = poolStats.Misses
    stats["conns"] = poolStats.TotalConns
    
    return stats
}
```

## Boas Práticas

1. **Sempre use context.Context** - permite cancelamento e timeout
2. **Defina TTLs apropriados** - evita acúmulo de dados obsoletos
3. **Implemente cache warming** - pré-carregue dados críticos
4. **Monitore hit/miss ratio** - ideal: > 80% hits
5. **Use consistent hashing** - se usar múltiplos nós Redis
6. **Implemente circuit breaker** - para falhas de conexão
7. **Serialize com JSON ou MessagePack** - evite gob para interoperabilidade

## Próximos Passos

Agora que você domina Go e Redis, explore:

- **[Go Concurrency Patterns](/tutoriais/go-concurrency-patterns/)** - Combine Redis com goroutines
- **[Go e gRPC](/tutoriais/go-grpc-tutorial/)** - Arquiteturas de microserviços
- **[Go Performance](/tutoriais/go-performance-profiling/)** - Otimize ainda mais suas aplicações

## FAQ

**Qual a diferença entre go-redis e redigo?**
O go-redis é mais moderno, ativamente mantido e possui melhor suporte a novas features do Redis 7.x.

**Redis é thread-safe em Go?**
Sim, o cliente go-redis é thread-safe e pode ser compartilhado entre múltiplas goroutines.

**Quando NÃO usar Redis?**
Evite para dados que precisam de ACID completo, grandes objetos binários (> 512MB) ou quando durabilidade é crítica.

**Como escalar Redis?**
Use Redis Cluster para particionamento automático ou Redis Sentinel para alta disponibilidade.

---

*Este tutorial foi útil? Compartilhe com outros desenvolvedores Go e deixe suas dúvidas nos comentários!*
