一、连接管理
1. 基础连接
go
go
import "github.com/redis/go-redis/v9"
// 单机连接
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // 无密码
DB: 0, // 默认DB
})
// 集群连接
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{":7000", ":7001", ":7002"},
})
2. 连接池配置
go
go
rdb := redis.NewClient(&redis.Options{
PoolSize: 100, // 连接池大小
MinIdleConns: 10, // 最小空闲连接
MaxRetries: 3, // 最大重试次数
DialTimeout: 5 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
PoolTimeout: 4 * time.Second,
})
二、String 字符串
基本操作
go
ctx := context.Background()
// SET/GET
rdb.Set(ctx, "key", "value", 0)
val, err := rdb.Get(ctx, "key").Result()
// 带过期时间
rdb.SetEx(ctx, "key", "value", 10*time.Second)
// 批量操作
rdb.MSet(ctx, "key1", "value1", "key2", "value2")
rdb.MGet(ctx, "key1", "key2")
// 自增/自减
rdb.Incr(ctx, "counter")
rdb.IncrBy(ctx, "counter", 5)
三、Hash 哈希表
go
go
// HSET/HGET
rdb.HSet(ctx, "user:1000", "name", "John", "age", 30)
name := rdb.HGet(ctx, "user:1000", "name").Val()
// 获取所有字段
all := rdb.HGetAll(ctx, "user:1000").Val()
// HMSET/HMGET
rdb.HMSet(ctx, "user:1001", map[string]interface{}{
"name": "Alice",
"age": 25,
})
// 字段自增
rdb.HIncrBy(ctx, "user:1000", "age", 1)
// 删除字段
rdb.HDel(ctx, "user:1000", "age")
四、List 列表
go
go
// LPUSH/RPUSH
rdb.LPush(ctx, "mylist", "value1", "value2")
rdb.RPush(ctx, "mylist", "value3")
// LPOP/RPOP
val := rdb.LPop(ctx, "mylist").Val()
// LRANGE
items := rdb.LRange(ctx, "mylist", 0, -1).Val()
// LTRIM 保留指定范围
rdb.LTrim(ctx, "mylist", 0, 9)
// 阻塞操作
result := rdb.BLPop(ctx, 5*time.Second, "mylist")
五、Set 集合
go
go
// SADD/SMEMBERS
rdb.SAdd(ctx, "myset", "member1", "member2")
members := rdb.SMembers(ctx, "myset").Val()
// SISMEMBER
exists := rdb.SIsMember(ctx, "myset", "member1").Val()
// 集合运算
rdb.SUnion(ctx, "set1", "set2") // 并集
rdb.SInter(ctx, "set1", "set2") // 交集
rdb.SDiff(ctx, "set1", "set2") // 差集
// SCARD 获取集合大小
size := rdb.SCard(ctx, "myset").Val()
六、Sorted Set 有序集合
go
go
// ZADD
rdb.ZAdd(ctx, "leaderboard", redis.Z{
Score: 100,
Member: "player1",
})
// 批量添加
members := []redis.Z{
{Score: 200, Member: "player2"},
{Score: 150, Member: "player3"},
}
rdb.ZAdd(ctx, "leaderboard", members...)
// ZRANGE 按分数排序
rdb.ZRange(ctx, "leaderboard", 0, -1) // 升序
rdb.ZRevRange(ctx, "leaderboard", 0, -1) // 降序
// 带分数返回
results := rdb.ZRangeWithScores(ctx, "leaderboard", 0, -1).Val()
// ZRANK 获取排名
rank := rdb.ZRank(ctx, "leaderboard", "player1").Val()
// ZSCORE 获取分数
score := rdb.ZScore(ctx, "leaderboard", "player1").Val()
// ZINCRBY 增加分数
rdb.ZIncrBy(ctx, "leaderboard", 50, "player1")
// ZCOUNT 分数区间计数
count := rdb.ZCount(ctx, "leaderboard", "100", "200").Val()
七、Stream 流
go
go
// XADD 添加消息
id, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
ID: "*", // 自动生成ID
Values: map[string]interface{}{
"field1": "value1",
"field2": "value2",
},
}).Result()
// XREAD 读取消息
messages, err := rdb.XRead(ctx, &redis.XReadArgs{
Streams: []string{"mystream", "0"},
Count: 10,
Block: 0,
}).Result()
// 消费者组
// 创建消费者组
rdb.XGroupCreate(ctx, "mystream", "mygroup", "0")
// 消费者读取
messages, err := rdb.XReadGroup(ctx, &redis.XReadGroupArgs{
Group: "mygroup",
Consumer: "consumer1",
Streams: []string{"mystream", ">"},
Count: 10,
Block: 0,
}).Result()
// 确认消息
rdb.XAck(ctx, "mystream", "mygroup", id)
八、事务
go
go
// MULTI/EXEC
pipe := rdb.TxPipeline()
pipe.Set(ctx, "key1", "value1", 0)
pipe.Incr(ctx, "counter")
pipe.Expire(ctx, "key1", time.Hour)
_, err := pipe.Exec(ctx)
// Watch 乐观锁
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
// 获取当前值
val, err := tx.Get(ctx, "key").Int()
if err != nil && err != redis.Nil {
return err
}
// 事务开始
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Set(ctx, "key", val+1, 0)
return nil
})
return err
}, "key")
九、发布订阅
go
go
// 创建订阅者
pubsub := rdb.Subscribe(ctx, "mychannel")
// 接收消息
ch := pubsub.Channel()
for msg := range ch {
fmt.Println(msg.Channel, msg.Payload)
}
// 发布消息
err := rdb.Publish(ctx, "mychannel", "hello").Err()
// 模式订阅
pubsub := rdb.PSubscribe(ctx, "news.*")
// 关闭订阅
pubsub.Close()
十、管道(Pipeline)
go
go
pipe := rdb.Pipeline()
pipe.Set(ctx, "key1", "value1", 0)
pipe.Get(ctx, "key1")
pipe.Expire(ctx, "key1", time.Hour)
cmds, err := pipe.Exec(ctx)
十一、实用技巧
1. 错误处理
go
go
val, err := rdb.Get(ctx, "key").Result()
if err == redis.Nil {
fmt.Println("key does not exist")
} else if err != nil {
panic(err)
}
2. Scan 命令处理大数据集
go
go
var cursor uint64
for {
var keys []string
keys, cursor, err = rdb.Scan(ctx, cursor, "pattern:*", 100).Result()
// 处理keys
if cursor == 0 {
break
}
}
3. 连接健康检查
go
err := rdb.Ping(ctx).Err()
if err != nil {
// 处理连接错误
}
十二、案例讲解
go
package main
import (
"context"
"fmt"
"log"
"math/rand"
"time"
"github.com/redis/go-redis/v9"
)
// ============================ 数据结构定义 ============================
// GameRankingSystem 游戏排行榜系统结构体
// 封装了Redis客户端和上下文,提供统一的游戏服务接口
type GameRankingSystem struct {
rdb *redis.Client // Redis客户端实例,用于所有Redis操作
ctx context.Context // 上下文,用于控制请求超时和取消
}
// User 用户结构体
// 表示游戏中的用户,包含用户的基本信息和资产
type User struct {
ID string // 用户唯一标识
Username string // 用户名
Level int // 用户等级
Coins int // 用户金币数量
}
// ============================ 系统初始化 ============================
// NewGameRankingSystem 创建排行榜系统实例
// 返回值: *GameRankingSystem 初始化好的游戏系统实例
func NewGameRankingSystem() *GameRankingSystem {
// 创建 Redis 客户端配置
// Options结构体包含了连接Redis所需的所有配置参数
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis服务器地址
Password: "", // 密码,空字符串表示无密码
DB: 0, // 数据库编号,0表示默认数据库
PoolSize: 20, // 连接池大小,控制最大并发连接数
})
// 创建上下文,用于控制请求的生命周期
ctx := context.Background()
// 测试连接是否成功
// Ping命令是Redis最简单的命令,用于测试连通性
if err := rdb.Ping(ctx).Err(); err != nil {
// 连接失败时,终止程序运行
log.Fatalf("Redis连接失败: %v", err)
}
// 返回初始化好的系统实例
return &GameRankingSystem{
rdb: rdb,
ctx: ctx,
}
}
// ============================ String 操作示例 ============================
// StringOperations 演示String类型的基本操作
// String是最简单的Redis数据类型,常用于存储简单的键值对
func (g *GameRankingSystem) StringOperations() {
fmt.Println("\n=== String 操作演示 ===")
// 1. 存储用户会话信息(String)
// 场景:用户登录后创建会话,30分钟后自动过期
// 键名模式:session:{userID},便于管理和查找
sessionKey := fmt.Sprintf("session:%s", "user123")
// SetEx命令:设置键值对并指定过期时间
// 参数说明:ctx上下文,key键名,value值,expiration过期时间
g.rdb.SetEx(g.ctx, sessionKey, "logged_in", 30*time.Minute)
fmt.Println("✅ 用户会话已设置,30分钟后过期")
// 2. 每日登录奖励计数器
// 场景:统计每天有多少用户登录,用于活动分析
// 键名模式:daily:login:{date},按日期组织数据
today := time.Now().Format("2006-01-02")
dailyLoginKey := fmt.Sprintf("daily:login:%s", today)
// Incr命令:将键存储的数字值增加1
// 如果键不存在,则会先创建并设置为0,再执行+1操作
count, _ := g.rdb.Incr(g.ctx, dailyLoginKey).Result()
// Expire命令:设置键的过期时间
// 这里设置48小时过期,保留2天的统计数据
g.rdb.Expire(g.ctx, dailyLoginKey, 48*time.Hour)
fmt.Printf("✅ 今日第 %d 位用户登录\n", count)
// 3. 获取会话状态
// 模拟用户访问时的会话验证
status, err := g.rdb.Get(g.ctx, sessionKey).Result()
if err == redis.Nil {
// redis.Nil是特殊的错误类型,表示键不存在
fmt.Println("❌ 会话不存在或已过期")
} else if err != nil {
// 其他错误类型,如网络错误、Redis服务错误等
fmt.Printf("❌ 获取会话失败: %v\n", err)
} else {
// 获取成功,输出会话状态
fmt.Printf("✅ 用户会话状态: %s\n", status)
}
}
// ============================ Hash 操作示例 ============================
// HashOperations 演示Hash类型的基本操作
// Hash是field-value结构,适合存储对象
func (g *GameRankingSystem) HashOperations(user User) {
fmt.Println("\n=== Hash 操作演示 ===")
// 用户信息键名
// 键名模式:user:{userID},使用Hash存储用户的所有属性
userKey := fmt.Sprintf("user:%s", user.ID)
// 1. 存储用户信息到 Hash(一个对象多个字段)
// HSet命令:设置Hash中的字段值
// 可以一次性设置多个字段,减少网络请求次数
g.rdb.HSet(g.ctx, userKey,
"username", user.Username, // 字符串字段
"level", user.Level, // 数字字段
"coins", user.Coins, // 数字字段
"last_login", time.Now().Format(time.RFC3339), // 时间字段
)
fmt.Println("✅ 用户信息已存储到 Hash")
// 2. 获取用户特定字段
// HGet命令:获取Hash中指定字段的值
username, _ := g.rdb.HGet(g.ctx, userKey, "username").Result()
coins, _ := g.rdb.HGet(g.ctx, userKey, "coins").Int()
fmt.Printf("✅ 用户名: %s, 金币数: %d\n", username, coins)
// 3. 增加用户金币(字段自增)
// HIncrBy命令:将Hash中指定字段的值增加给定数值
// 原子操作,适用于计数器和余额增减场景
newCoins, _ := g.rdb.HIncrBy(g.ctx, userKey, "coins", 100).Result()
fmt.Printf("✅ 获得100金币奖励,当前金币: %d\n", newCoins)
// 4. 获取所有用户信息
// HGetAll命令:获取Hash中所有字段和值
// 返回map[string]string类型,适用于获取完整对象
allInfo, _ := g.rdb.HGetAll(g.ctx, userKey).Result()
fmt.Printf("✅ 用户完整信息: %v\n", allInfo)
// 5. 检查字段是否存在
// HExists命令:检查Hash中指定字段是否存在
exists, _ := g.rdb.HExists(g.ctx, userKey, "level").Result()
fmt.Printf("✅ level字段是否存在: %v\n", exists)
}
// ============================ Sorted Set 操作示例 ============================
// SortedSetOperations 演示Sorted Set类型的基本操作
// Sorted Set是有序集合,每个元素都有一个分数用于排序
func (g *GameRankingSystem) SortedSetOperations(users []User) {
fmt.Println("\n=== Sorted Set 操作演示 ===")
// 排行榜键名
leaderboardKey := "leaderboard:global"
// 1. 批量添加用户到排行榜(ZADD)
// 准备要添加到Sorted Set的成员列表
var members []redis.Z // redis.Z是Sorted Set成员的结构体
for _, user := range users {
// 计算用户分数
// 分数设计:level * 1000 + coins
// 这样高等级玩家总是排在低等级玩家前面,同等级玩家按金币数排序
score := float64(user.Level*1000 + user.Coins)
members = append(members, redis.Z{
Score: score, // 排序分数
Member: user.Username, // 成员标识
})
}
// ZAdd命令:向Sorted Set添加成员
// 如果成员已存在,则更新其分数
g.rdb.ZAdd(g.ctx, leaderboardKey, members...)
fmt.Println("✅ 用户排行榜已更新")
// 2. 获取前3名玩家(ZREVRANGE)
// 降序排列,分数高的在前面
fmt.Println("\n🏆 排行榜前三名:")
top3, _ := g.rdb.ZRevRangeWithScores(g.ctx, leaderboardKey, 0, 2).Result()
for i, player := range top3 {
fmt.Printf("第%d名: %s (分数: %.0f)\n", i+1, player.Member, player.Score)
}
// 3. 获取用户排名(ZREVRANK)
// 获取成员在Sorted Set中的排名(从0开始)
for _, user := range users {
rank, err := g.rdb.ZRevRank(g.ctx, leaderboardKey, user.Username).Result()
if err == redis.Nil {
// 用户不在排行榜中
fmt.Printf("❌ 用户 %s 不在排行榜中\n", user.Username)
} else {
// 排名从0开始,所以需要+1显示人类可读的排名
fmt.Printf("👤 用户 %s 的排名: 第%d名\n", user.Username, rank+1)
}
}
// 4. 获取分数段玩家数量(ZCOUNT)
// 统计指定分数范围内的成员数量
minScore := "5000"
maxScore := "8000"
count, _ := g.rdb.ZCount(g.ctx, leaderboardKey, minScore, maxScore).Result()
fmt.Printf("📊 分数在 %s-%s 之间的玩家数: %d\n", minScore, maxScore, count)
}
// ============================ Set 操作示例 ============================
// SetOperations 演示Set类型的基本操作
// Set是无序集合,元素不重复,支持集合运算
func (g *GameRankingSystem) SetOperations(user User) {
fmt.Println("\n=== Set 操作演示 ===")
// 1. 用户好友集合
// 键名模式:user:{userID}:friends
friendsKey := fmt.Sprintf("user:%s:friends", user.ID)
// SAdd命令:向Set添加成员
// 如果成员已存在,则不会重复添加
g.rdb.SAdd(g.ctx, friendsKey, "user456", "user789", "user999")
fmt.Println("✅ 好友列表已添加")
// 2. 获取所有好友
// SMembers命令:获取Set中所有成员
// 注意:Set是无序的,每次返回的顺序可能不同
friends, _ := g.rdb.SMembers(g.ctx, friendsKey).Result()
fmt.Printf("👥 %s 的好友列表: %v\n", user.Username, friends)
// 3. 检查是否是好友
// SIsMember命令:检查指定成员是否在Set中
isFriend, _ := g.rdb.SIsMember(g.ctx, friendsKey, "user456").Result()
fmt.Printf("❓ user456 是否是好友: %v\n", isFriend)
// 4. 随机推荐好友(SRANDMEMBER)
// SRandMember命令:从Set中随机返回一个成员
// 可用于好友推荐、抽奖等场景
randomFriend, _ := g.rdb.SRandMember(g.ctx, friendsKey).Result()
fmt.Printf("🎲 随机推荐好友: %s\n", randomFriend)
// 5. 好友数量
// SCard命令:获取Set中成员的数量
friendCount, _ := g.rdb.SCard(g.ctx, friendsKey).Result()
fmt.Printf("📊 好友总数: %d\n", friendCount)
}
// ============================ List 操作示例 ============================
// ListOperations 演示List类型的基本操作
// List是有序列表,元素可以重复,支持从两端操作
func (g *GameRankingSystem) ListOperations(user User) {
fmt.Println("\n=== List 操作演示 ===")
// 1. 用户最近游戏记录(List作为队列)
// 键名模式:user:{userID}:game_history
// 使用List存储用户最近的游戏记录
gameHistoryKey := fmt.Sprintf("user:%s:game_history", user.ID)
// 添加最近的游戏记录
// LPush命令:从列表左侧插入元素,最新记录在最前面
for i := 1; i <= 5; i++ {
gameInfo := fmt.Sprintf("游戏%d_得分%d_时间%s",
i,
rand.Intn(1000), // 随机生成得分
time.Now().Format("15:04:05")) // 当前时间
g.rdb.LPush(g.ctx, gameHistoryKey, gameInfo)
}
fmt.Println("✅ 游戏记录已保存")
// 2. 获取最近3场游戏(LRANGE)
// LRange命令:获取列表中指定范围的元素
// 参数:0表示第一个元素,2表示第三个元素(包含)
fmt.Println("\n🎮 最近3场游戏记录:")
recentGames, _ := g.rdb.LRange(g.ctx, gameHistoryKey, 0, 2).Result()
for i, game := range recentGames {
fmt.Printf(" %d. %s\n", i+1, game)
}
// 3. 保持最近10条记录(LTRIM)
// LTrim命令:修剪列表,只保留指定范围内的元素
// 参数:0表示起始索引,9表示结束索引
// 常用于实现固定大小的历史记录列表
g.rdb.LTrim(g.ctx, gameHistoryKey, 0, 9)
fmt.Println("✅ 已修剪,只保留最近10条记录")
// 4. 获取列表长度
// LLen命令:获取列表的长度
historyLength, _ := g.rdb.LLen(g.ctx, gameHistoryKey).Result()
fmt.Printf("📊 游戏历史记录总数: %d\n", historyLength)
}
// ============================ 事务操作示例 ============================
// TransactionOperations 演示事务操作
// Redis事务可以保证多个命令的原子性执行
func (g *GameRankingSystem) TransactionOperations(user1, user2 User) {
fmt.Println("\n=== 事务操作演示 ===")
// 场景:用户之间转账金币,需要保证原子性
// 必须确保扣款和入账同时成功或同时失败
// 用户信息键名
user1Key := fmt.Sprintf("user:%s", user1.ID)
user2Key := fmt.Sprintf("user:%s", user2.ID)
transferAmount := 50 // 转账金额
// 使用事务管道(TxPipeline)
fmt.Printf("💰 开始转账: %s 给 %s 转账 %d 金币\n",
user1.Username, user2.Username, transferAmount)
// 开始事务
// Watch命令:监控指定的键,如果在事务执行期间被其他客户端修改,则事务失败
// 这是Redis的乐观锁机制
err := g.rdb.Watch(g.ctx, func(tx *redis.Tx) error {
// 在Watch回调中,所有操作都必须通过tx对象执行
// 获取双方当前金币数量
coins1, err := tx.HGet(g.ctx, user1Key, "coins").Int()
if err != nil && err != redis.Nil {
return err
}
coins2, err := tx.HGet(g.ctx, user2Key, "coins").Int()
if err != nil && err != redis.Nil {
return err
}
fmt.Printf("转账前: %s=%d金币, %s=%d金币\n",
user1.Username, coins1, user2.Username, coins2)
// 检查余额是否足够
// 业务逻辑检查必须在事务中执行
if coins1 < transferAmount {
return fmt.Errorf("余额不足")
}
// 执行事务
// TxPipelined命令:在事务中执行多个命令
_, err = tx.TxPipelined(g.ctx, func(pipe redis.Pipeliner) error {
// 扣减转出方金币
// HIncrBy命令的负数参数表示减少
pipe.HIncrBy(g.ctx, user1Key, "coins", int64(-transferAmount))
// 增加转入方金币
pipe.HIncrBy(g.ctx, user2Key, "coins", int64(transferAmount))
// 记录转账日志(可选,用于审计)
transferLog := fmt.Sprintf("transfer:%s:%s:%d",
user1.ID, user2.ID, time.Now().Unix())
pipe.Set(g.ctx, transferLog, transferAmount, 24*time.Hour)
return nil
})
return err
}, user1Key, user2Key) // Watch 监视的键,这些键在事务期间不能改变
// 处理事务结果
if err != nil {
fmt.Printf("❌ 转账失败: %v\n", err)
} else {
fmt.Println("✅ 转账成功!")
// 验证转账结果
newCoins1, _ := g.rdb.HGet(g.ctx, user1Key, "coins").Int()
newCoins2, _ := g.rdb.HGet(g.ctx, user2Key, "coins").Int()
fmt.Printf("转账后: %s=%d金币, %s=%d金币\n",
user1.Username, newCoins1, user2.Username, newCoins2)
}
}
// ============================ 发布订阅示例 ============================
// PubSubOperations 演示发布订阅模式
// Redis Pub/Sub 是一种消息通信模式,发送者发送消息,订阅者接收消息
func (g *GameRankingSystem) PubSubOperations() {
fmt.Println("\n=== 发布订阅演示 ===")
channelName := "game:announcements" // 频道名称
// 启动一个goroutine作为订阅者
// 在实际应用中,订阅者通常是独立的服务或进程
go func() {
// Subscribe命令:订阅指定频道
pubsub := g.rdb.Subscribe(g.ctx, channelName)
// defer确保在函数退出时关闭订阅,释放资源
defer pubsub.Close()
// Channel方法:返回一个Go channel,用于接收消息
ch := pubsub.Channel()
fmt.Println("👂 订阅者已启动,等待消息...")
// 循环接收消息
for msg := range ch {
// msg包含频道名称和消息内容
fmt.Printf("📢 收到公告: %s\n", msg.Payload)
// 收到特定消息后退出
// 这是演示用的控制逻辑,实际应用中可能不需要
if msg.Payload == "exit" {
fmt.Println("⏹️ 订阅者退出")
return
}
}
}()
// 给订阅者一点时间启动,确保订阅者已经连接
// 在实际生产环境中,应该有更好的同步机制
time.Sleep(100 * time.Millisecond)
// 发布消息
fmt.Println("\n📡 发布游戏公告...")
// 模拟要发布的消息列表
announcements := []string{
"系统维护通知:今晚23:00-24:00进行维护",
"新活动上线:周末双倍经验活动开始!",
"恭喜玩家 '游戏高手' 获得全服首杀!",
"exit", // 特殊消息,让订阅者退出
}
// 依次发布每条消息
for _, msg := range announcements {
// Publish命令:向指定频道发布消息
// 所有订阅了该频道的客户端都会收到消息
g.rdb.Publish(g.ctx, channelName, msg)
// 模拟延迟,让输出更清晰
time.Sleep(500 * time.Millisecond)
}
}
// ============================ 批量操作示例 ============================
// PipelineOperations 演示Pipeline批量操作
// Pipeline可以将多个命令一次性发送给Redis,减少网络往返时间
func (g *GameRankingSystem) PipelineOperations(users []User) {
fmt.Println("\n=== Pipeline 批量操作演示 ===")
// 使用 Pipeline 批量执行命令,减少网络往返
// Pipeline不是事务,不保证原子性,但能提高性能
pipe := g.rdb.Pipeline()
// 批量准备命令
for _, user := range users {
userKey := fmt.Sprintf("user:%s:stats", user.ID)
// 批量设置命令
// 这些命令会被缓存,不会立即发送到Redis
pipe.HSet(g.ctx, userKey,
"kills", rand.Intn(100), // 随机生成击杀数
"deaths", rand.Intn(50), // 随机生成死亡数
"assists", rand.Intn(30), // 随机生成助攻数
)
// 设置过期时间
pipe.Expire(g.ctx, userKey, time.Hour)
}
// 执行所有命令
fmt.Println("🚀 正在批量执行命令...")
start := time.Now()
// Exec命令:一次性发送所有缓存的命令到Redis
cmds, err := pipe.Exec(g.ctx)
elapsed := time.Since(start)
if err != nil {
fmt.Printf("❌ Pipeline执行失败: %v\n", err)
return
}
// 输出执行结果
fmt.Printf("✅ 批量执行完成,处理了 %d 个用户\n", len(users))
fmt.Printf("⏱️ 总耗时: %v (使用普通命令需 %v 左右)\n",
elapsed, time.Duration(len(users)*2)*time.Millisecond)
// cmds包含每个命令的执行结果,如果需要可以进一步处理
_ = cmds // 这里不需要使用,所以用下划线忽略
}
// ============================ 清理数据 ============================
// Cleanup 清理测试数据
// 在生产环境中应该小心使用,这里是为了演示后清理测试数据
func (g *GameRankingSystem) Cleanup(patterns ...string) {
fmt.Println("\n=== 清理测试数据 ===")
// 遍历所有要清理的模式
for _, pattern := range patterns {
// 使用 SCAN 迭代删除,避免阻塞
// SCAN命令比KEYS命令更安全,不会阻塞Redis服务
var cursor uint64
for {
var foundKeys []string
var err error
// SCAN命令:迭代遍历匹配模式的键
// 参数说明:cursor游标,pattern匹配模式,count每次返回的键数量
foundKeys, cursor, err = g.rdb.Scan(g.ctx, cursor, pattern, 10).Result()
if err != nil {
break
}
// 如果找到匹配的键,删除它们
if len(foundKeys) > 0 {
// Del命令:删除一个或多个键
g.rdb.Del(g.ctx, foundKeys...)
fmt.Printf("🧹 已删除: %v\n", foundKeys)
}
// cursor为0表示迭代结束
if cursor == 0 {
break
}
}
}
fmt.Println("✅ 数据清理完成")
}
// ============================ 主函数 ============================
// main 程序入口函数
func main() {
fmt.Println("🎮 Redis 游戏排行榜系统演示")
fmt.Println("=" * 50)
// 初始化系统
// 创建GameRankingSystem实例,建立Redis连接
gameSystem := NewGameRankingSystem()
// defer确保程序退出时关闭Redis连接,释放资源
defer func() {
if err := gameSystem.rdb.Close(); err != nil {
fmt.Printf("关闭Redis连接时出错: %v\n", err)
}
}()
// 创建测试用户数据
// 这些用户用于演示各种Redis操作
users := []User{
{ID: "1001", Username: "游戏高手", Level: 10, Coins: 5000},
{ID: "1002", Username: "幸运玩家", Level: 8, Coins: 3000},
{ID: "1003", Username: "新手玩家", Level: 3, Coins: 1000},
{ID: "1004", Username: "氪金大佬", Level: 15, Coins: 10000},
}
// 初始化随机数种子
// 确保每次运行生成的随机数不同
rand.Seed(time.Now().UnixNano())
// 执行各个功能演示
// 按顺序演示Redis的各种数据结构和功能
// 1. String操作演示
gameSystem.StringOperations()
// 2. Hash操作演示(使用第一个用户)
gameSystem.HashOperations(users[0])
// 3. Sorted Set操作演示(排行榜功能)
gameSystem.SortedSetOperations(users)
// 4. Set操作演示(好友功能)
gameSystem.SetOperations(users[0])
// 5. List操作演示(游戏历史记录)
gameSystem.ListOperations(users[0])
// 6. 事务操作演示(转账功能)
gameSystem.TransactionOperations(users[0], users[1])
// 7. Pipeline批量操作演示
gameSystem.PipelineOperations(users)
// 8. 发布订阅演示(需要异步执行)
gameSystem.PubSubOperations()
// 等待发布订阅演示完成
// 因为发布订阅是异步的,需要等待足够的时间让消息被处理
time.Sleep(3 * time.Second)
// 清理测试数据
// 删除演示过程中创建的所有测试数据,避免影响下次运行
gameSystem.Cleanup(
"user:*", // 所有用户数据
"session:*", // 会话数据
"daily:*", // 每日统计数据
"leaderboard:*", // 排行榜数据
"transfer:*", // 转账日志数据
)
fmt.Println("\n🎉 演示完成!")
fmt.Println("\n📚 总结:")
fmt.Println("1. String: 适合简单键值对、计数器和缓存")
fmt.Println("2. Hash: 适合存储对象和结构化数据")
fmt.Println("3. Sorted Set: 适合排行榜和范围查询")
fmt.Println("4. Set: 适合集合运算和不重复列表")
fmt.Println("5. List: 适合队列和历史记录")
fmt.Println("6. 事务: 适合需要原子性的操作")
fmt.Println("7. Pipeline: 适合批量操作,提高性能")
fmt.Println("8. Pub/Sub: 适合实时消息通知")
}
核心知识点总结:
-
连接管理最佳实践
连接池配置:PoolSize 控制最大并发连接数,避免连接过多
连接测试:使用 Ping() 验证连接有效性
资源释放:使用 defer 确保连接关闭,防止资源泄漏
-
数据结构选择原则
String:简单缓存、计数器、会话管理
Hash:用户信息、商品信息等结构化数据
Sorted Set:排行榜、带权重的队列
Set:好友关系、标签系统、共同关注
List:消息队列、最新动态、操作日志
-
事务 vs Pipeline
事务(Transaction):保证原子性,适合转账、库存扣减
流水线(Pipeline):提高性能,适合批量操作但不需原子性
-
性能优化技巧
使用Pipeline:减少网络往返,批量操作提升性能
合理设置过期时间:避免内存泄漏,自动清理过期数据
使用SCAN替代KEYS:避免阻塞Redis服务
连接池配置:根据并发量调整连接池大小
-
错误处理要点
区分redis.Nil:键不存在的特殊错误,需单独处理
事务失败处理:Watch失败时重试或回退
连接错误:实现重连机制和降级策略
-
实际应用场景
游戏系统:排行榜(Sorted Set)、用户信息(Hash)、好友(Set)
电商系统:购物车(Hash)、商品库存(String)、订单队列(List)
社交系统:关注关系(Set)、动态流(List)、消息通知(Pub/Sub)