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

简介: 本文详解 Redis 在 Go 中的 10 大核心应用场景:缓存、会话存储、限流、排行榜、消息队列、发布订阅、实时分析、分布式锁、地理位置、购物车,并提供完整可运行代码与最佳实践,助你高效构建高性能应用。(239字)

📦 环境准备

# 安装 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()

相关文章
|
1月前
|
数据采集 存储 人工智能
阿里云为何要将数据采集开发套件开源
开源 LoongSuite ,成为 AI 可观测体系中的一块通用拼图。
262 37
|
1月前
|
人工智能 自然语言处理 数据可视化
作为一名在读博士生,我在日常是如何与 AI 协作的
本文是一位AI方向博士生的AI协作实践手记:主张“当同事,不当工具”,提出元提示词、苏格拉底追问、多模型协同与经验沉淀四大方法论,覆盖划词问答、文献研读、科研绘图、代码开发等全科研场景,强调人机共生、流程提效与持续进化。
|
1月前
|
人工智能 安全 Linux
OpenClaw(Clawdbot)喂饭级指南:阿里云/本地部署、百炼API配置、核心Skill获取、实用技巧与避坑手册
OpenClaw(原Clawdbot,俗称“小龙虾”)作为2026年热门的开源AI Agent框架,其设计核心是“基础框架+第三方技能”的模块化架构——官方仅提供基础交互能力,真正的智能性需通过安装适配场景的Skills实现。多数用户部署后觉得“不够智能”,核心问题并非工具本身,而是未掌握技能拓展与使用技巧。
920 1
|
1月前
|
人工智能 机器人 API
飞书/钉钉/QQ 机器人一站式搞定!OpenClaw Docker 部署教程
OpenClaw-Docker-CN-IM 是一款开箱即用的国产IM机器人网关Docker镜像,预装飞书、钉钉、QQ、企业微信等插件,支持环境变量灵活配置;集成OpenCode AI代码助手、Playwright自动化及中文TTS,助力开发者快速部署多平台AI机器人。
1520 3
|
1月前
|
人工智能 自然语言处理 API
AI 变身股票分析师!OpenClaw阿里云/本地部署+集成股票 Skill,一键获取A股行情与潜力股推荐
OpenClaw(昵称“大龙虾”)的核心优势在于“既有AI的大脑,又有干活的双手”——它不仅能理解自然语言指令,更能通过Skill(技能)插件执行具体任务。对投资者而言,Stock-Analysis技能的出现彻底改变了传统股票分析模式:无需手动抓取数据、无需编写复杂脚本,仅需一句自然语言指令,就能让AI完成实时行情分析、板块筛选、潜力股推荐、早盘报告生成等专业操作,将原本需要数小时的分析工作压缩至分钟级。
4920 0
|
1月前
|
安全 编译器 Go
Go 类型系统的“隐形特权”:无类型常量
Go中`const`是被低估的“隐形特权”:无类型常量无需声明类型、支持无限精度运算(如`1&lt;&lt;100`)、可隐式适配多种类型,且编译期高精度计算。它灵活安全,但变量必须有类型——因内存布局需运行时确定。善用`const`,兼顾简洁与性能。(239字)
136 2
|
1月前
|
Prometheus JavaScript Cloud Native
Fiber v3 适配器模式:17 种写法随便用,老代码“即插即用“
Fiber v3 适配器模式提供「万能转换插头」,无缝兼容 4 大类、17 种 Handler(原生 Fiber / net/http / fasthttp / Express 风格),让老代码零修改复用、新接口高效开发、团队平滑迁移,真正实现业务不中断、升级无压力!
166 2
|
2月前
|
缓存 NoSQL 网络协议
Redis Pipeline 实战指南:提升 Go 后端性能的利器
Redis Pipeline 是一种批量命令优化机制,通过一次网络往返执行多条命令,显著降低RTT开销、提升吞吐(实测快50倍)。它非事务,无原子性保证,适用于批量写入、排行榜更新等场景;强一致性需求应选MULTI/EXEC或Lua脚本。(239字)
250 1