Databases 18 min read

Top 10 Redis Use Cases with Go: Practical Guide

This guide walks through ten classic Redis scenarios—caching, session storage, rate limiting, leaderboards, message queues, Pub/Sub, real‑time analytics, distributed locks, geospatial queries, and shopping carts—providing Go code examples, best‑practice tips, and performance considerations for each pattern.

Golang Shines
Golang Shines
Golang Shines
Top 10 Redis Use Cases with Go: Practical Guide

Environment Setup

Install the Go Redis client and start Redis with Docker.

go get github.com/redis/go-redis/v9
docker run -d -p 6379:6379 redis:latest

1️⃣ Caching (Cache‑Aside)

Store frequently accessed data in Redis strings with a TTL to reduce database load.

func getUserWithCache(userID string) (*User, error) {
    key := fmt.Sprintf("user:%s", userID)
    data, err := rdb.Get(ctx, key).Result()
    if err == nil {
        var user User
        json.Unmarshal([]byte(data), &user)
        return &user, nil
    }
    user, err := getUserFromDB(userID)
    if err != nil {
        return nil, err
    }
    userData, _ := json.Marshal(user)
    rdb.Set(ctx, key, userData, 30*time.Minute).Err()
    return user, nil
}
💡 Set a reasonable TTL (5‑30 minutes) to avoid stale data.

2️⃣ Session Store

Stateless session management using Redis strings or hashes.

type Session struct {
    UserID    string `json:"user_id"`
    Username  string `json:"username"`
    LoginAt   time.Time `json:"login_at"`
    ExpiresAt time.Time `json:"expires_at"`
}

func createSession(userID, username string) (string, error) {
    sessionID := generateUUID()
    session := &Session{
        UserID:    userID,
        Username:  username,
        LoginAt:   time.Now(),
        ExpiresAt: time.Now().Add(24 * time.Hour),
    }
    key := fmt.Sprintf("session:%s", sessionID)
    data, _ := json.Marshal(session)
    err := rdb.Set(ctx, key, data, 24*time.Hour).Err()
    return sessionID, err
}

3️⃣ Rate Limiting

Protect APIs with a fixed‑window counter or a sliding‑window (token bucket) algorithm.

// Fixed‑window
func rateLimit(userID string, limit int, window time.Duration) bool {
    key := fmt.Sprintf("ratelimit:%s", userID)
    count, _ := rdb.Incr(ctx, key).Result()
    if count == 1 {
        rdb.Expire(ctx, key, window)
    }
    return count <= int64(limit)
}

// Sliding‑window (token bucket)
func slidingWindowRateLimit(userID string, limit int, window time.Duration) bool {
    key := fmt.Sprintf("ratelimit:sliding:%s", userID)
    now := time.Now().UnixNano()
    start := now - window.Nanoseconds()
    rdb.ZRemRangeByScore(ctx, key, "0", fmt.Sprintf("%d", start))
    rdb.ZAdd(ctx, key, &redis.Z{Score: float64(now), Member: fmt.Sprintf("%d", now)})
    rdb.Expire(ctx, key, window)
    cnt, _ := rdb.ZCard(ctx, key).Result()
    return cnt <= int64(limit)
}
⚠️ Choose fixed‑window for simplicity; sliding‑window provides finer granularity.

4️⃣ Leaderboard

Maintain real‑time rankings with a sorted set.

func addPlayerScore(playerID string, score int64) error {
    return rdb.ZAdd(ctx, "leaderboard:global", &redis.Z{Score: float64(score), Member: playerID}).Err()
}

func getPlayerRank(playerID string) (int64, error) {
    rank, err := rdb.ZRevRank(ctx, "leaderboard:global", playerID).Result()
    if err != nil {
        return 0, err
    }
    return rank + 1, nil // 1‑based rank
}

5️⃣ Message Queue

Lightweight queue using a Redis list.

func enqueue(queueName string, message interface{}) error {
    data, _ := json.Marshal(message)
    return rdb.RPush(ctx, fmt.Sprintf("queue:%s", queueName), data).Err()
}

func dequeue(queueName string, timeout time.Duration) ([]byte, error) {
    result, err := rdb.BLPop(ctx, timeout, fmt.Sprintf("queue:%s", queueName)).Result()
    if err != nil {
        return nil, err
    }
    return []byte(result[1]), nil
}

6️⃣ Pub/Sub

Real‑time message broadcasting.

func publish(channel string, message interface{}) error {
    data, _ := json.Marshal(message)
    return rdb.Publish(ctx, channel, data).Err()
}

func subscribe(channels ...string) *redis.PubSub {
    ps := rdb.Subscribe(ctx, channels...)
    _, err := ps.Receive(ctx)
    if err != nil {
        panic(err)
    }
    return ps
}

func receiveMessage(ps *redis.PubSub) (*redis.Message, error) {
    return ps.ReceiveMessage(ctx)
}
⚠️ Pub/Sub messages are not persisted; offline subscribers lose messages.

7️⃣ Real‑time Analytics

Track page views (PV) and unique visitors (UV) using counters and HyperLogLog.

func recordPageView(pageID, userID string) error {
    key := fmt.Sprintf("analytics:page:%s", pageID)
    rdb.PFAdd(ctx, key, userID)               // UV
    rdb.Incr(ctx, fmt.Sprintf("analytics:pv:%s", pageID)) // PV
    return rdb.Expire(ctx, key, 7*24*time.Hour).Err()
}

func getUniqueVisitors(pageID string) (int64, error) {
    return rdb.PFCount(ctx, fmt.Sprintf("analytics:page:%s", pageID)).Result()
}

func getPageViews(pageID string) (int64, error) {
    return rdb.Get(ctx, fmt.Sprintf("analytics:pv:%s", pageID)).Int64()
}

8️⃣ Distributed Lock

Ensure exclusive access across multiple instances.

func acquireLock(lockKey, lockID string, ttl time.Duration) bool {
    result, err := rdb.Set(ctx, lockKey, lockID, &redis.SetArgs{NX: true, EX: int64(ttl.Seconds())}).Result()
    return err == nil && result == "OK"
}

func releaseLock(lockKey, lockID string) bool {
    lua := `if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end`
    n, _ := rdb.Eval(ctx, lua, []string{lockKey}, lockID).Int64()
    return n == 1
}
⚠️ Verify lock ID on release to avoid deleting another client’s lock.

9️⃣ Geospatial

Store and query locations for O2O services.

type Location struct {
    Name      string
    Longitude float64
    Latitude  float64
}

func addLocation(key string, loc Location) error {
    return rdb.GeoAdd(ctx, key, &redis.GeoLocation{
        Name:      loc.Name,
        Longitude: loc.Longitude,
        Latitude:  loc.Latitude,
    }).Err()
}

func findNearby(key string, lon, lat, radius float64) ([]redis.GeoLocation, error) {
    return rdb.GeoRadius(ctx, key, lon, lat, &redis.GeoRadiusQuery{
        Radius:   radius,
        Unit:     "km",
        WithDist: true,
    }).Result()
}

func getDistance(key, loc1, loc2 string) (float64, error) {
    return rdb.GeoDist(ctx, key, loc1, loc2, "km").Result()
}

🔟 Shopping Cart

Implement an e‑commerce cart using a hash; each product is stored as JSON and its quantity in a separate field.

type CartItem struct {
    ProductID string  `json:"product_id"`
    Name      string  `json:"name"`
    Price     float64 `json:"price"`
    Quantity  int     `json:"quantity"`
}

func addToCart(userID, productID string, quantity int) error {
    key := fmt.Sprintf("cart:%s", userID)
    exists, _ := rdb.HExists(ctx, key, productID).Result()
    if exists {
        return rdb.HIncrBy(ctx, key, fmt.Sprintf("%s:qty", productID), int64(quantity)).Err()
    }
    product := getProduct(productID) // fetch from DB
    data, _ := json.Marshal(product)
    if err := rdb.HSet(ctx, key, productID, string(data)).Err(); err != nil {
        return err
    }
    return rdb.HSet(ctx, key, fmt.Sprintf("%s:qty", productID), quantity).Err()
}

func getCart(userID string) ([]CartItem, error) {
    key := fmt.Sprintf("cart:%s", userID)
    data, _ := rdb.HGetAll(ctx, key).Result()
    var items []CartItem
    for field, jsonData := range data {
        if strings.HasSuffix(field, ":qty") {
            continue
        }
        var item CartItem
        json.Unmarshal([]byte(jsonData), &item)
        qty, _ := rdb.HGet(ctx, key, fmt.Sprintf("%s:qty", field)).Int()
        item.Quantity = qty
        items = append(items, item)
    }
    return items, nil
}

Best Practices

// Connection pool
rdb := redis.NewClient(&redis.Options{
    Addr:         "localhost:6379",
    PoolSize:     100,
    MinIdleConns: 10,
    MaxRetries:   3,
    PoolTimeout:  30 * time.Second,
})

// Health check
func checkRedisHealth() bool {
    _, err := rdb.Ping(ctx).Result()
    return err == nil
}

// Graceful shutdown
defer rdb.Close()
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RedisGoCachingMessage QueueRate LimitingShopping CartPub/SubLeaderboard
Golang Shines
Written by

Golang Shines

We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.