Using Redis in Go: Global Client with Auto Reconnect


September 19, 2025
Program

Using Redis in Go: Global Client with Auto Reconnect


A practical guide to integrating Redis in Go using a global client with auto-reconnect, cache-aside pattern, and best practices for robust, maintainable caching.

Redis is a powerful in-memory data store often used for caching, sessions, and rate limiting.

When integrating Redis into a Go application, you need to decide:

  • Where to place your Redis client (global vs dependency injection)
  • How to handle Redis downtime (graceful fallback, auto reconnect)
  • Which layer should own caching logic (repository, service, or handler)

This guide shows a practical industry-style setup:

  • A global Redis client (pkg/redisclient)
  • Auto reconnect & retry settings
  • Example of caching user data inside the repository

Install go-redis

🔗
sh
go get github.com/redis/go-redis/v9

Create a Global Redis Client

🔗

We put Redis inside pkg/redisclient so it can be reused across the whole project without injecting it everywhere.

pkg/redisclient/redis.go

go
package redisclient

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

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

var Rdb *redis.Client

// Init initializes the global Redis client
func Init() {
    _ = godotenv.Load()

    host := os.Getenv("REDIS_HOST")
    if host == "" {
        host = "127.0.0.1"
    }
    port := os.Getenv("REDIS_PORT")
    if port == "" {
        port = "6379"
    }
    password := os.Getenv("REDIS_PASSWORD")

    addr := fmt.Sprintf("%s:%s", host, port)

    Rdb = redis.NewClient(&redis.Options{
        Addr:         addr,
        Password:     password,
        DB:           0,
    })

    if _, err := Rdb.Ping(context.Background()).Result(); err != nil {
        log.Printf("⚠️ Redis unavailable: %v", err)
        Rdb = nil // mark as unavailable
    } else {
        log.Println("✅ Redis connected successfully")
    }
}

Why global?

  • Simplicity: Handlers and services can access redisclient.Rdb directly.
  • Performance: Only one client instance; go-redis internally manages connection pooling.
  • Industry practice: Redis is usually an infrastructure service, not per-request state.

Initialize Redis in main.go

🔗
go
package main

import (
    "go-template/pkg/redisclient"
)

func main() {
    redisclient.Init()
    // start Gin or other services...
}

Use Redis in the Repository Layer

🔗

Instead of putting caching inside handlers (which makes them “fat”), we keep caching at the repository layer, since caching is part of “data access”.

go
// internal/user/repository/user_repository.go
func (r *userRepository) GetUserByID(id int64) (*model.User, string, error) {
    cacheKey := fmt.Sprintf("user:%d", id)

    // 1. Try Redis first
    if redisclient.Rdb != nil {
        if val, err := redisclient.Rdb.Get(context.Background(), cacheKey).Result(); err == nil {
            var u model.User
            if json.Unmarshal([]byte(val), &u) == nil {
                return &u, "redis", nil
            }
        }
    }

    // 2. Fallback to DB
    var u model.User
    err := r.DB.QueryRow(
        `SELECT id, name, email, role FROM "user" WHERE id = $1`,
        id,
    ).Scan(&u.ID, &u.Name, &u.Email, &u.Role)

    if err == sql.ErrNoRows {
        return nil, "db", nil
    } else if err != nil {
        return nil, "db", err
    }

    // 3. Write back to Redis
    if redisclient.Rdb != nil {
        if bytes, err := json.Marshal(u); err == nil {
            _ = redisclient.Rdb.Set(context.Background(), cacheKey, bytes, 10*time.Minute).Err()
        }
    }

    return &u, "db", nil
}

Now the service just calls the repository, and it doesn’t matter if data came from Redis or the DB.


Handling Redis Downtime

🔗
  • On startup, if Redis is down → log a warning and continue.
  • On runtime, if Redis fails (redis.Nil or connection error) → simply fallback to DB.
  • The application never crashes because of Redis.

This approach is called Cache-Aside Pattern:

  • Read: Try cache → fallback DB → write back to cache.
  • Write: Update DB → invalidate or update cache.

When to Use Global vs Injection?

🔗
ApproachWhen to UseProsCons
Global client (pkg/redisclient.Rdb)Small/medium projects, caching as infraSimple, reusable everywhereHarder to mock in tests
Dependency injectionLarge projects, strict testabilityExplicit dependencies, easier mockingMore boilerplate (passing rdb everywhere)

Industry trend: Most production Go backends use a global client for Redis, since it’s infrastructure like logging or config. If you’re building a highly modular system or a library, injection may be better.


Summary

🔗
  • Use pkg/redisclient with a global client for simplicity.
  • Configure MaxRetries, DialTimeout, and Ping to handle downtime gracefully.
  • Place caching logic in repository or service, not in handlers.
  • Follow the Cache-Aside Pattern for reliable caching.

This ensures:

  • Your app still runs if Redis is down.
  • Redis auto-reconnects when it comes back.
  • Handlers stay clean, services focus on logic, and repositories handle data + cache.

GoCachingRedis



Avatar

Alvin

Software engineer who dislikes pointless busyness, enjoys solving problems with logic, and strives to find balance between the blind pursuit of achievements and a relaxed lifestyle.

Related Posts