📦 环境准备
# 安装 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()