Redis 十大经典使用场景 - Go 语言实战指南

举报
golang学习记 发表于 2026/03/06 14:01:54 2026/03/06
【摘要】 📦 环境准备# 安装 Redis Go 客户端go get github.com/redis/go-redis/v9# 启动 Redis(Docker)docker run -d -p 6379:6379 redis:latestpackage mainimport ( "context" "encoding/json" "fmt" "time" "...

📦 环境准备

# 安装 Redis Go 客户端
go get github.com/redis/go-redis/v9

# 启动 Redis(Docker)
docker run -d -p 6379:6379 redis:latest
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "time"
    
    "github.com/redis/go-redis/v9"
)

var ctx = context.Background()
var rdb *redis.Client

func init() {
    rdb = redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
}

1️⃣ 缓存(Caching)🔥

最经典的使用场景,将频繁访问的数据存入内存,减轻数据库压力 [[1]]。

工作原理

┌─────────────┐     ┌──────────┐     ┌──────────┐
│   客户端     │ ──> │  Redis   │ ──> │ 数据库    │
│             │ <──  (缓存命中)│     │          │
└─────────────┘     └──────────┘     └──────────┘

Go 代码实现

// 缓存旁路模式(Cache-Aside)
func getUserWithCache(userID string) (*User, error) {
    key := fmt.Sprintf("user:%s", userID)
    
    // 1. 先查 Redis
    data, err := rdb.Get(ctx, key).Result()
    if err == nil {
        // 缓存命中
        var user User
        json.Unmarshal([]byte(data), &user)
        return &user, nil
    }
    
    // 2. 缓存未命中,查数据库
    user, err := getUserFromDB(userID)
    if err != nil {
        return nil, err
    }
    
    // 3. 写入 Redis,设置 TTL
    userData, _ := json.Marshal(user)
    rdb.Set(ctx, key, userData, 30*time.Minute)
    
    return user, nil
}

// 带过期时间的缓存
func setCache(key string, value interface{}, ttl time.Duration) error {
    data, _ := json.Marshal(value)
    return rdb.Set(ctx, key, data, ttl).Err()
}

💡 最佳实践:设置合理的 TTL 避免脏数据,推荐使用 5-30 分钟


2️⃣ 会话存储(Session Store)🔐

现代 Web 应用需要无状态会话管理,Redis 是完美选择 [[2]]。

Go 代码实现

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)
    
    // 设置 24 小时过期
    err := rdb.Set(ctx, key, data, 24*time.Hour).Err()
    return sessionID, err
}

// 获取会话
func getSession(sessionID string) (*Session, error) {
    key := fmt.Sprintf("session:%s", sessionID)
    data, err := rdb.Get(ctx, key).Result()
    if err != nil {
        return nil, err
    }
    
    var session Session
    json.Unmarshal([]byte(data), &session)
    return &session, nil
}

// 删除会话(登出)
func deleteSession(sessionID string) error {
    key := fmt.Sprintf("session:%s", sessionID)
    return rdb.Del(ctx, key).Err()
}

⚠️ 注意:生产环境建议配置 Redis 持久化(RDB/AOF)或主从复制


3️⃣ 限流(Rate Limiting)🛑

保护 API 不被滥用,固定窗口滑动窗口算法

Go 代码实现(令牌桶算法)

// 基于 INCR 的固定窗口限流
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)
}

// 滑动窗口限流(更精确)
func slidingWindowRateLimit(userID string, limit int, window time.Duration) bool {
    key := fmt.Sprintf("ratelimit:sliding:%s", userID)
    now := time.Now().UnixNano()
    windowStart := now - window.Nanoseconds()
    
    // 移除过期请求
    rdb.ZRemRangeByScore(ctx, key, "0", fmt.Sprintf("%d", windowStart))
    
    // 添加当前请求
    rdb.ZAdd(ctx, key, redis.Z{Score: float64(now), Member: fmt.Sprintf("%d", now)})
    
    // 设置过期
    rdb.Expire(ctx, key, window)
    
    // 统计请求数
    count, _ := rdb.ZCard(ctx, key).Result()
    return count <= int64(limit)
}
// 中间件使用示例
func RateLimitMiddleware(limit int, window time.Duration) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            userID := r.RemoteAddr
            if !rateLimit(userID, limit, window) {
                http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

4️⃣ 排行榜(Leaderboard)🏆

使用 Sorted Set 实现实时排名,游戏场景必备

Go 代码实现

// 添加玩家分数
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) {
    // ZRANK 返回从 0 开始的排名,+1 得到实际排名
    rank, err := rdb.ZRevRank(ctx, "leaderboard:global", playerID).Result()
    if err != nil {
        return 0, err
    }
    return rank + 1, nil
}

// 获取 Top N 玩家
func getTopPlayers(n int64) ([]redis.Z, error) {
    return rdb.ZRevRangeWithScores(ctx, "leaderboard:global", 0, n-1).Result()
}

// 获取玩家分数
func getPlayerScore(playerID string) (float64, error) {
    return rdb.ZScore(ctx, "leaderboard:global", playerID).Result()
}
// 使用示例
func main() {
    addPlayerScore("player_001", 1500)
    addPlayerScore("player_002", 2300)
    addPlayerScore("player_003", 1800)
    
    rank, _ := getPlayerRank("player_001")
    fmt.Printf("玩家排名:%d\n", rank) // 输出:玩家排名:3
    
    topPlayers, _ := getTopPlayers(3)
    for i, player := range topPlayers {
        fmt.Printf("第%d名:%s - %.0f分\n", i+1, player.Member, player.Score)
    }
}

💡 技巧ZRevRange 按分数降序,ZRange 按分数升序


5️⃣ 消息队列(Message Queue)📨

使用 List 实现轻量级消息队列 [[7]]。

Go 代码实现

// 生产者:入队
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 := rdb.BLPop(ctx, timeout, fmt.Sprintf("queue:%s", queueName))
    if result.Err() != nil {
        return nil, result.Err()
    }
    
    // BLPop 返回 [queueName, message]
    values, _ := result.Result()
    return []byte(values[1]), nil
}

// 消费者:出队(非阻塞)
func dequeueNonBlock(queueName string) ([]byte, error) {
    result := rdb.LPop(ctx, fmt.Sprintf("queue:%s", queueName))
    if result.Err() == redis.Nil {
        return nil, nil // 队列为空
    }
    return []byte(result.Val()), result.Err()
}

// 获取队列长度
func getQueueLength(queueName string) (int64, error) {
    return rdb.LLen(ctx, fmt.Sprintf("queue:%s", queueName)).Result()
}
// 使用示例
go func() {
    // 生产者
    for i := 0; i < 10; i++ {
        enqueue("tasks", Task{ID: i, Data: fmt.Sprintf("task_%d", i)})
    }
}()

go func() {
    // 消费者
    for {
        data, _ := dequeue("tasks", 5*time.Second)
        if data != nil {
            var task Task
            json.Unmarshal(data, &task)
            processTask(task)
        }
    }
}()

6️⃣ 发布订阅(Pub/Sub)📢

实现实时消息推送,聊天室、通知系统必备

Go 代码实现

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

// 订阅者
func subscribe(channels ...string) *redis.PubSub {
    pubsub := rdb.Subscribe(ctx, channels...)
    
    // 确认订阅成功
    _, err := pubsub.Receive(ctx)
    if err != nil {
        panic(err)
    }
    
    return pubsub
}

// 接收消息
func receiveMessage(pubsub *redis.PubSub) (*redis.Message, error) {
    msg, err := pubsub.ReceiveMessage(ctx)
    return msg, err
}
// 使用示例
func main() {
    // 启动订阅者
    pubsub := subscribe("chat:room1", "chat:room2")
    defer pubsub.Close()
    
    go func() {
        for {
            msg, _ := receiveMessage(pubsub)
            fmt.Printf("收到消息 [%s]: %s\n", msg.Channel, msg.Payload)
        }
    }()
    
    // 发布消息
    publish("chat:room1", Message{User: "Alice", Text: "Hello!"})
    publish("chat:room2", Message{User: "Bob", Text: "Hi there!"})
    
    time.Sleep(time.Second)
}

⚠️ 注意:Pub/Sub 消息不持久化,消费者离线会丢失消息


7️⃣ 实时分析(Real-time Analytics)📊

统计页面访问、用户行为等实时数据

Go 代码实现

// 记录页面访问
func recordPageView(pageID, userID string) error {
    key := fmt.Sprintf("analytics:page:%s", pageID)
    
    // 使用 HyperLogLog 统计 UV(去重)
    rdb.PFAdd(ctx, key, userID)
    
    // 使用 Counter 统计 PV
    rdb.Incr(ctx, fmt.Sprintf("analytics:pv:%s", pageID))
    
    // 设置过期时间(如 7 天)
    rdb.Expire(ctx, key, 7*24*time.Hour)
    
    return nil
}

// 获取 UV(独立访客数)
func getUniqueVisitors(pageID string) (int64, error) {
    key := fmt.Sprintf("analytics:page:%s", pageID)
    return rdb.PFCount(ctx, key).Result()
}

// 获取 PV(页面访问量)
func getPageViews(pageID string) (int64, error) {
    return rdb.Get(ctx, fmt.Sprintf("analytics:pv:%s", pageID)).Int64()
}

// 记录用户在线状态
func setUserOnline(userID string) error {
    key := "analytics:online:users"
    return rdb.SAdd(ctx, key, userID).Err()
}

// 获取在线用户数
func getOnlineUserCount() (int64, error) {
    return rdb.SCard(ctx, "analytics:online:users").Result()
}

💡 HyperLogLog:基数统计神器,12KB 内存可统计 20 亿独立元素!


8️⃣ 分布式锁(Distributed Lock)🔒

多实例场景下保证资源互斥访问

Go 代码实现

// 获取分布式锁
func acquireLock(lockKey, lockID string, ttl time.Duration) bool {
    // SET key value NX EX ttl(原子操作)
    result, err := rdb.Set(ctx, lockKey, lockID, 
        redis.SetArgs{
            NX: true,           // 不存在才设置
            EX: int64(ttl.Seconds()),
        }).Result()
    
    return err == nil && result == "OK"
}

// 释放分布式锁(需要 Lua 脚本保证原子性)
func releaseLock(lockKey, lockID string) bool {
    luaScript := `
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
    `
    
    result, _ := rdb.Eval(ctx, luaScript, []string{lockKey}, lockID).Int64()
    return result == 1
}

// 使用示例
func processWithLock(resourceID string) error {
    lockKey := fmt.Sprintf("lock:resource:%s", resourceID)
    lockID := generateUUID()
    
    if !acquireLock(lockKey, lockID, 30*time.Second) {
        return errors.New("获取锁失败")
    }
    
    defer releaseLock(lockKey, lockID)
    
    // 执行临界区代码
    return doCriticalWork()
}

⚠️ 重要:释放锁时必须验证锁 ID,防止误删其他进程的锁!


9️⃣ 地理位置(Geospatial)🌍

存储和查询地理位置数据,O2O 应用必备 [[5]]。

Go 代码实现

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, longitude, latitude float64, radius float64) ([]redis.GeoLocation, error) {
    result, err := rdb.GeoRadius(ctx, key, longitude, latitude, 
        &redis.GeoRadiusQuery{
            Radius: radius,
            Unit:   "km",
            WithDist: true,
        }).Result()
    
    if err != nil {
        return nil, err
    }
    
    locations := make([]redis.GeoLocation, len(result))
    for i, r := range result {
        locations[i] = r.GeoLocation
    }
    return locations, nil
}

// 计算两地距离
func getDistance(key string, loc1, loc2 string) (float64, error) {
    return rdb.GeoDist(ctx, key, loc1, loc2, "km").Result()
}
// 使用示例
func main() {
    // 添加城市位置
    addLocation("cities", Location{"北京", 116.4074, 39.9042})
    addLocation("cities", Location{"上海", 121.4737, 31.2304})
    addLocation("cities", Location{"广州", 113.2644, 23.1291})
    
    // 查找北京附近 1000km 内的城市
    nearby, _ := findNearby("cities", 116.4074, 39.9042, 1000)
    for _, loc := range nearby {
        fmt.Printf("%s - 距离:%.2fkm\n", loc.Name, loc.Dist)
    }
}

🔟 购物车(Shopping Cart)🛒

电商场景经典应用,使用 Hash 存储商品信息

Go 代码实现

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 {
        // 已存在,增加数量
        rdb.HIncrBy(ctx, key, fmt.Sprintf("%s:qty", productID), int64(quantity))
    } else {
        // 新商品,存储完整信息
        product := getProduct(productID) // 从数据库获取
        data, _ := json.Marshal(product)
        rdb.HSet(ctx, key, productID, string(data))
        rdb.HSet(ctx, key, fmt.Sprintf("%s:qty", productID), quantity)
    }
    
    // 设置 30 天过期
    rdb.Expire(ctx, key, 30*24*time.Hour)
    
    return nil
}

// 获取购物车
func getCart(userID string) ([]CartItem, error) {
    key := fmt.Sprintf("cart:%s", userID)
    
    // 获取所有商品
    data, _ := rdb.HGetAll(ctx, key).Result()
    
    var items []CartItem
    for productID, jsonData := range data {
        if strings.HasSuffix(productID, ":qty") {
            continue // 跳过数量字段
        }
        
        var item CartItem
        json.Unmarshal([]byte(jsonData), &item)
        
        qtyKey := fmt.Sprintf("%s:qty", productID)
        qty, _ := rdb.HGet(ctx, key, qtyKey).Int64()
        item.Quantity = int(qty)
        
        items = append(items, item)
    }
    
    return items, nil
}

// 更新商品数量
func updateCartQuantity(userID, productID string, quantity int) error {
    key := fmt.Sprintf("cart:%s", userID)
    return rdb.HSet(ctx, key, fmt.Sprintf("%s:qty", productID), quantity).Err()
}

// 删除商品
func removeFromCart(userID, productID string) error {
    key := fmt.Sprintf("cart:%s", userID)
    rdb.HDel(ctx, key, productID, fmt.Sprintf("%s:qty", productID))
    return nil
}

// 清空购物车
func clearCart(userID string) error {
    key := fmt.Sprintf("cart:%s", userID)
    return rdb.Del(ctx, key).Err()
}

📊 总结对比

场景 数据结构 核心命令 适用场景
🥇 缓存 String SET/GET + EXPIRE 数据库查询加速
🔐 会话 String/Hash SET/GET + EXPIRE 用户登录状态
🛑 限流 String/Sorted Set INCR/ZADD API 保护
🏆 排行榜 Sorted Set ZADD/ZRANK 游戏/积分排名
📨 消息队列 List RPUSH/BLPOP 异步任务处理
📢 发布订阅 Pub/Sub PUBLISH/SUBSCRIBE 实时通知
📊 实时分析 HyperLogLog/Set PFADD/SADD UV/PV 统计
🔒 分布式锁 String SET NX 资源互斥
🌍 地理位置 Geo GEOADD/GEORADIUS O2O/附近的人
🛒 购物车 Hash HSET/HGET 电商购物车

🎯 最佳实践

// 1. 连接池配置
rdb := redis.NewClient(&redis.Options{
    Addr:         "localhost:6379",
    PoolSize:     100,              // 连接池大小
    MinIdleConns: 10,               // 最小空闲连接
    MaxRetries:   3,                // 最大重试次数
    PoolTimeout:  30 * time.Second, // 连接超时
})

// 2. 健康检查
func checkRedisHealth() bool {
    _, err := rdb.Ping(ctx).Result()
    return err == nil
}

// 3. 优雅关闭
defer rdb.Close()

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。