Redis实战指南:从基础概念到Go语言应用
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,它不仅可以用作数据库、缓存和消息代理,还支持多种数据结构,如字符串、哈希、列表、集合和有序集合等。今天我们将通过实际的Go语言代码示例来深入了解Redis的使用。
Redis基础数据结构及操作
1. 字符串(String)
字符串是Redis最基本的数据类型,可以存储文本、数字或二进制数据。
GO
package main
import (
"context"
"fmt"
"time"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
var rdb *redis.Client
func init() {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
}
func stringOperations() {
// 设置键值对
err := rdb.Set(ctx, "name", "Redis", 0).Err()
if err != nil {
panic(err)
}
// 获取键值
val, err := rdb.Get(ctx, "name").Result()
if err != nil {
panic(err)
}
fmt.Println("name:", val)
// 设置带过期时间的键值对
err = rdb.Set(ctx, "counter", 0, 1*time.Hour).Err()
if err != nil {
panic(err)
}
// 自增操作
err = rdb.Incr(ctx, "counter").Err()
if err != nil {
panic(err)
}
val, err = rdb.Get(ctx, "counter").Result()
if err != nil {
panic(err)
}
fmt.Println("counter:", val)
}
2. 哈希(Hash)
哈希允许我们将多个键值对存储在一个键下,非常适合存储对象。
GO
func hashOperations() {
// 设置哈希字段
err := rdb.HSet(ctx, "user:1000", "name", "张三").Err()
if err != nil {
panic(err)
}
err = rdb.HSet(ctx, "user:1000", "email", "zhangsan@example.com").Err()
if err != nil {
panic(err)
}
// 获取单个字段
name, err := rdb.HGet(ctx, "user:1000", "name").Result()
if err != nil {
panic(err)
}
fmt.Println("user name:", name)
// 获取所有字段
userMap, err := rdb.HGetAll(ctx, "user:1000").Result()
if err != nil {
panic(err)
}
fmt.Println("user info:", userMap)
}
3. 列表(List)
列表是一个有序的字符串列表,支持在两端插入和删除元素。
GO
func listOperations() {
// 向列表左侧插入元素
err := rdb.LPush(ctx, "tasks", "task1", "task2", "task3").Err()
if err != nil {
panic(err)
}
// 向列表右侧插入元素
err = rdb.RPush(ctx, "tasks", "task4").Err()
if err != nil {
panic(err)
}
// 从左侧弹出元素
task, err := rdb.LPop(ctx, "tasks").Result()
if err != nil {
panic(err)
}
fmt.Println("popped task:", task)
// 获取列表长度
length, err := rdb.LLen(ctx, "tasks").Result()
if err != nil {
panic(err)
}
fmt.Println("tasks count:", length)
// 获取列表中的所有元素
tasks, err := rdb.LRange(ctx, "tasks", 0, -1).Result()
if err != nil {
panic(err)
}
fmt.Println("remaining tasks:", tasks)
}
4. 集合(Set)
集合是无序的字符串集合,支持交集、并集、差集等操作。
GO
func setOperations() {
// 向集合添加元素
err := rdb.SAdd(ctx, "tags:post:1", "go", "redis", "database").Err()
if err != nil {
panic(err)
}
err = rdb.SAdd(ctx, "tags:post:2", "go", "web", "api").Err()
if err != nil {
panic(err)
}
// 检查元素是否在集合中
isMember, err := rdb.SIsMember(ctx, "tags:post:1", "redis").Result()
if err != nil {
panic(err)
}
fmt.Println("is 'redis' in post1 tags:", isMember)
// 获取集合中的所有元素
tags, err := rdb.SMembers(ctx, "tags:post:1").Result()
if err != nil {
panic(err)
}
fmt.Println("post1 tags:", tags)
// 集合并集操作
union, err := rdb.SUnion(ctx, "tags:post:1", "tags:post:2").Result()
if err != nil {
panic(err)
}
fmt.Println("union of tags:", union)
// 集合交集操作
intersect, err := rdb.SInter(ctx, "tags:post:1", "tags:post:2").Result()
if err != nil {
panic(err)
}
fmt.Println("intersection of tags:", intersect)
}
5. 有序集合(Sorted Set)
有序集合与集合类似,但每个元素都关联一个分数,可以根据分数排序。
GO
func sortedSetOperations() {
// 向有序集合添加元素
err := rdb.ZAdd(ctx, "leaderboard",
&redis.Z{Score: 100, Member: "player1"},
&redis.Z{Score: 90, Member: "player2"},
&redis.Z{Score: 95, Member: "player3"}).Err()
if err != nil {
panic(err)
}
// 增加成员分数
err = rdb.ZIncrBy(ctx, "leaderboard", 10, "player2").Err()
if err != nil {
panic(err)
}
// 获取按分数排序的前N个成员
players, err := rdb.ZRevRangeWithScores(ctx, "leaderboard", 0, 2).Result()
if err != nil {
panic(err)
}
fmt.Println("Leaderboard:")
for i, player := range players {
fmt.Printf("%d. %v - %v points\n", i+1, player.Member, player.Score)
}
}
实际应用场景代码示例
1. 缓存应用
GO
func getCachedUser(userID string) (map[string]string, error) {
// 首先尝试从缓存获取
cachedUser, err := rdb.HGetAll(ctx, "user:"+userID).Result()
if err != nil && err != redis.Nil {
return nil, err
}
// 如果缓存中存在数据
if len(cachedUser) > 0 && cachedUser["name"] != "" {
fmt.Println("从缓存获取用户信息")
return cachedUser, nil
}
// 模拟从数据库获取数据
fmt.Println("从数据库获取用户信息")
user := map[string]string{
"id": userID,
"name": "张三",
"email": "zhangsan@example.com",
}
// 将数据存入缓存,设置过期时间
err = rdb.HMSet(ctx, "user:"+userID, user).Err()
if err != nil {
return nil, err
}
// 设置过期时间
rdb.Expire(ctx, "user:"+userID, 10*time.Minute)
return user, nil
}
2. 分布式锁
GO
func acquireLock(lockKey string, timeout time.Duration) (string, error) {
// 生成唯一标识
lockValue := fmt.Sprintf("%d", time.Now().UnixNano())
// 尝试获取锁,设置过期时间防止死锁
ok, err := rdb.SetNX(ctx, lockKey, lockValue, timeout).Result()
if err != nil {
return "", err
}
if !ok {
return "", fmt.Errorf("无法获取锁")
}
return lockValue, nil
}
func releaseLock(lockKey, lockValue string) error {
// 使用Lua脚本确保原子性
script := `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
`
result, err := rdb.Eval(ctx, script, []string{lockKey}, lockValue).Result()
if err != nil {
return err
}
if result.(int64) == 0 {
return fmt.Errorf("锁已失效或被其他进程持有")
}
return nil
}
func doWithLock(lockKey string, fn func() error) error {
lockValue, err := acquireLock(lockKey, 10*time.Second)
if err != nil {
return err
}
defer releaseLock(lockKey, lockValue)
return fn()
}
3. 消息队列
GO
// 生产者
func produceMessage(queueName, message string) error {
return rdb.LPush(ctx, queueName, message).Err()
}
// 消费者
func consumeMessage(queueName string) (string, error) {
// 阻塞式弹出消息,超时时间为1秒
result, err := rdb.BRPop(ctx, 1*time.Second, queueName).Result()
if err != nil {
return "", err
}
// BRPop返回的是包含键名和值的数组,我们只需要值
if len(result) >= 2 {
return result[1], nil
}
return "", fmt.Errorf("无法获取消息")
}
func messageQueueExample() {
queueName := "task_queue"
// 启动生产者
go func() {
for i := 0; i < 5; i++ {
message := fmt.Sprintf("task_%d", i)
err := produceMessage(queueName, message)
if err != nil {
fmt.Printf("生产消息失败: %v\n", err)
} else {
fmt.Printf("生产消息: %s\n", message)
}
time.Sleep(1 * time.Second)
}
}()
// 启动消费者
go func() {
for {
message, err := consumeMessage(queueName)
if err != nil {
if err != redis.Nil {
fmt.Printf("消费消息失败: %v\n", err)
}
time.Sleep(1 * time.Second)
continue
}
fmt.Printf("消费消息: %s\n", message)
// 模拟处理时间
time.Sleep(2 * time.Second)
}
}()
}
完整示例
GO
func main() {
// 测试连接
pong, err := rdb.Ping(ctx).Result()
if err != nil {
fmt.Println("连接Redis失败:", err)
return
}
fmt.Println("连接成功:", pong)
// 执行各种操作
fmt.Println("=== 字符串操作 ===")
stringOperations()
fmt.Println("\n=== 哈希操作 ===")
hashOperations()
fmt.Println("\n=== 列表操作 ===")
listOperations()
fmt.Println("\n=== 集合操作 ===")
setOperations()
fmt.Println("\n=== 有序集合操作 ===")
sortedSetOperations()
fmt.Println("\n=== 缓存示例 ===")
user, err := getCachedUser("123")
if err != nil {
fmt.Printf("获取用户失败: %v\n", err)
} else {
fmt.Printf("获取用户: %+v\n", user)
}
// 再次获取同一用户,应该从缓存获取
user, err = getCachedUser("123")
if err != nil {
fmt.Printf("获取用户失败: %v\n", err)
} else {
fmt.Printf("获取用户: %+v\n", user)
}
fmt.Println("\n=== 分布式锁示例 ===")
err = doWithLock("test_lock", func() error {
fmt.Println("执行需要锁保护的操作")
time.Sleep(3 * time.Second)
return nil
})
if err != nil {
fmt.Printf("执行锁保护操作失败: %v\n", err)
}
fmt.Println("\n=== 消息队列示例 ===")
messageQueueExample()
// 等待一段时间以便观察消息队列示例的输出
time.Sleep(10 * time.Second)
}