Skip to main content
使用 go-redis 操作 Redis,实现缓存、分布式锁等功能。

安装

go get github.com/redis/go-redis/v9

连接 Redis

单机连接

import (
    "context"
    "github.com/redis/go-redis/v9"
)

var rdb *redis.Client

func initRedis() {
    rdb = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",  // 密码
        DB:       0,   // 数据库
        PoolSize: 100, // 连接池大小
    })
    
    // 测试连接
    ctx := context.Background()
    _, err := rdb.Ping(ctx).Result()
    if err != nil {
        panic(err)
    }
}

集群连接

rdb := redis.NewClusterClient(&redis.ClusterOptions{
    Addrs: []string{
        "localhost:7000",
        "localhost:7001",
        "localhost:7002",
    },
    Password: "",
})

哨兵连接

rdb := redis.NewFailoverClient(&redis.FailoverOptions{
    MasterName:    "mymaster",
    SentinelAddrs: []string{"localhost:26379"},
    Password:      "",
    DB:            0,
})

基本操作

String 操作

ctx := context.Background()

// 设置值
err := rdb.Set(ctx, "key", "value", 0).Err()  // 0 表示不过期
err := rdb.Set(ctx, "key", "value", time.Hour).Err()  // 1小时过期

// 设置值(不存在时)
ok, err := rdb.SetNX(ctx, "key", "value", time.Hour).Result()

// 设置值(存在时)
ok, err := rdb.SetXX(ctx, "key", "value", time.Hour).Result()

// 获取值
val, err := rdb.Get(ctx, "key").Result()
if err == redis.Nil {
    fmt.Println("key 不存在")
}

// 批量设置
err := rdb.MSet(ctx, "key1", "val1", "key2", "val2").Err()

// 批量获取
vals, err := rdb.MGet(ctx, "key1", "key2").Result()

// 自增
n, err := rdb.Incr(ctx, "counter").Result()
n, err := rdb.IncrBy(ctx, "counter", 10).Result()

// 自减
n, err := rdb.Decr(ctx, "counter").Result()
n, err := rdb.DecrBy(ctx, "counter", 10).Result()

// 追加
length, err := rdb.Append(ctx, "key", "append").Result()

// 获取长度
length, err := rdb.StrLen(ctx, "key").Result()

Hash 操作

ctx := context.Background()

// 设置单个字段
err := rdb.HSet(ctx, "user:1", "name", "张三").Err()

// 设置多个字段
err := rdb.HSet(ctx, "user:1", map[string]interface{}{
    "name":  "张三",
    "age":   25,
    "email": "zhangsan@example.com",
}).Err()

// 获取单个字段
val, err := rdb.HGet(ctx, "user:1", "name").Result()

// 获取多个字段
vals, err := rdb.HMGet(ctx, "user:1", "name", "age").Result()

// 获取所有字段
fields, err := rdb.HGetAll(ctx, "user:1").Result()

// 字段是否存在
exists, err := rdb.HExists(ctx, "user:1", "name").Result()

// 删除字段
n, err := rdb.HDel(ctx, "user:1", "age").Result()

// 字段自增
n, err := rdb.HIncrBy(ctx, "user:1", "age", 1).Result()

// 获取所有字段名
keys, err := rdb.HKeys(ctx, "user:1").Result()

// 获取所有字段值
vals, err := rdb.HVals(ctx, "user:1").Result()

// 获取字段数量
length, err := rdb.HLen(ctx, "user:1").Result()

List 操作

ctx := context.Background()

// 左侧插入
n, err := rdb.LPush(ctx, "list", "a", "b", "c").Result()

// 右侧插入
n, err := rdb.RPush(ctx, "list", "d", "e").Result()

// 左侧弹出
val, err := rdb.LPop(ctx, "list").Result()

// 右侧弹出
val, err := rdb.RPop(ctx, "list").Result()

// 获取范围
vals, err := rdb.LRange(ctx, "list", 0, -1).Result()  // 获取全部

// 获取长度
length, err := rdb.LLen(ctx, "list").Result()

// 获取索引位置的值
val, err := rdb.LIndex(ctx, "list", 0).Result()

// 设置索引位置的值
err := rdb.LSet(ctx, "list", 0, "new").Err()

// 删除元素
n, err := rdb.LRem(ctx, "list", 1, "a").Result()  // 删除1个值为"a"的元素

// 阻塞弹出
val, err := rdb.BLPop(ctx, time.Second*5, "list").Result()
val, err := rdb.BRPop(ctx, time.Second*5, "list").Result()

Set 操作

ctx := context.Background()

// 添加成员
n, err := rdb.SAdd(ctx, "set", "a", "b", "c").Result()

// 获取所有成员
members, err := rdb.SMembers(ctx, "set").Result()

// 判断是否是成员
isMember, err := rdb.SIsMember(ctx, "set", "a").Result()

// 获取成员数量
count, err := rdb.SCard(ctx, "set").Result()

// 删除成员
n, err := rdb.SRem(ctx, "set", "a").Result()

// 随机获取成员
member, err := rdb.SRandMember(ctx, "set").Result()

// 随机弹出成员
member, err := rdb.SPop(ctx, "set").Result()

// 集合运算
// 交集
vals, err := rdb.SInter(ctx, "set1", "set2").Result()
// 并集
vals, err := rdb.SUnion(ctx, "set1", "set2").Result()
// 差集
vals, err := rdb.SDiff(ctx, "set1", "set2").Result()

Sorted Set 操作

ctx := context.Background()

// 添加成员
n, err := rdb.ZAdd(ctx, "zset", redis.Z{Score: 100, Member: "张三"}).Result()

// 批量添加
n, err := rdb.ZAdd(ctx, "zset",
    redis.Z{Score: 90, Member: "李四"},
    redis.Z{Score: 80, Member: "王五"},
).Result()

// 获取分数
score, err := rdb.ZScore(ctx, "zset", "张三").Result()

// 分数自增
newScore, err := rdb.ZIncrBy(ctx, "zset", 10, "张三").Result()

// 获取排名(从小到大)
rank, err := rdb.ZRank(ctx, "zset", "张三").Result()

// 获取排名(从大到小)
rank, err := rdb.ZRevRank(ctx, "zset", "张三").Result()

// 按排名获取(从小到大)
vals, err := rdb.ZRange(ctx, "zset", 0, -1).Result()

// 按排名获取(从大到小)
vals, err := rdb.ZRevRange(ctx, "zset", 0, 9).Result()  // Top 10

// 按分数范围获取
vals, err := rdb.ZRangeByScore(ctx, "zset", &redis.ZRangeBy{
    Min: "80",
    Max: "100",
}).Result()

// 获取成员数量
count, err := rdb.ZCard(ctx, "zset").Result()

// 删除成员
n, err := rdb.ZRem(ctx, "zset", "张三").Result()

// 按排名删除
n, err := rdb.ZRemRangeByRank(ctx, "zset", 0, 2).Result()

高级操作

管道(Pipeline)

ctx := context.Background()

// 管道:批量发送命令,减少网络往返
pipe := rdb.Pipeline()

pipe.Set(ctx, "key1", "value1", 0)
pipe.Set(ctx, "key2", "value2", 0)
pipe.Get(ctx, "key1")
pipe.Get(ctx, "key2")

cmds, err := pipe.Exec(ctx)
if err != nil {
    panic(err)
}

for _, cmd := range cmds {
    fmt.Println(cmd.(*redis.StringCmd).Val())
}

事务

ctx := context.Background()

// 事务:原子性执行多个命令
_, err := rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
    pipe.Set(ctx, "key1", "value1", 0)
    pipe.Set(ctx, "key2", "value2", 0)
    return nil
})

// Watch:乐观锁
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
    // 获取当前值
    n, err := tx.Get(ctx, "counter").Int()
    if err != nil && err != redis.Nil {
        return err
    }
    
    // 在事务中修改
    _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
        pipe.Set(ctx, "counter", n+1, 0)
        return nil
    })
    return err
}, "counter")

Lua 脚本

ctx := context.Background()

// 简单脚本
script := redis.NewScript(`
    return redis.call('set', KEYS[1], ARGV[1])
`)
err := script.Run(ctx, rdb, []string{"key"}, "value").Err()

// 复杂脚本(原子操作)
script := redis.NewScript(`
    local current = redis.call('get', KEYS[1])
    if current == false then
        return redis.call('set', KEYS[1], ARGV[1])
    end
    return nil
`)
result, err := script.Run(ctx, rdb, []string{"key"}, "value").Result()

分布式锁

简单实现

func TryLock(ctx context.Context, key string, value string, expiration time.Duration) bool {
    ok, err := rdb.SetNX(ctx, key, value, expiration).Result()
    if err != nil {
        return false
    }
    return ok
}

func Unlock(ctx context.Context, key string, value string) bool {
    // 使用 Lua 脚本保证原子性
    script := redis.NewScript(`
        if redis.call('get', KEYS[1]) == ARGV[1] then
            return redis.call('del', KEYS[1])
        else
            return 0
        end
    `)
    result, err := script.Run(ctx, rdb, []string{key}, value).Int()
    return err == nil && result == 1
}

// 使用
lockKey := "lock:resource"
lockValue := uuid.New().String()

if TryLock(ctx, lockKey, lockValue, time.Second*10) {
    defer Unlock(ctx, lockKey, lockValue)
    // 执行业务逻辑
}

使用 Redsync

import (
    "github.com/go-redsync/redsync/v4"
    "github.com/go-redsync/redsync/v4/redis/goredis/v9"
)

pool := goredis.NewPool(rdb)
rs := redsync.New(pool)

mutex := rs.NewMutex("lock:resource",
    redsync.WithExpiry(time.Second*10),
    redsync.WithTries(3),
)

if err := mutex.Lock(); err != nil {
    // 获取锁失败
    return err
}
defer mutex.Unlock()

// 执行业务逻辑

缓存模式

Cache Aside

func GetUser(ctx context.Context, userID int) (*User, error) {
    // 1. 先查缓存
    key := fmt.Sprintf("user:%d", userID)
    val, err := rdb.Get(ctx, key).Result()
    if err == nil {
        var user User
        json.Unmarshal([]byte(val), &user)
        return &user, nil
    }
    
    // 2. 缓存未命中,查数据库
    user, err := db.GetUserByID(userID)
    if err != nil {
        return nil, err
    }
    
    // 3. 写入缓存
    data, _ := json.Marshal(user)
    rdb.Set(ctx, key, data, time.Hour)
    
    return user, nil
}

func UpdateUser(ctx context.Context, user *User) error {
    // 1. 更新数据库
    if err := db.UpdateUser(user); err != nil {
        return err
    }
    
    // 2. 删除缓存
    key := fmt.Sprintf("user:%d", user.ID)
    rdb.Del(ctx, key)
    
    return nil
}

缓存穿透(布隆过滤器)

import "github.com/bits-and-blooms/bloom/v3"

var filter *bloom.BloomFilter

func init() {
    // 创建布隆过滤器
    filter = bloom.NewWithEstimates(1000000, 0.01)
    
    // 加载已有数据
    users, _ := db.GetAllUserIDs()
    for _, id := range users {
        filter.AddString(fmt.Sprintf("%d", id))
    }
}

func GetUser(ctx context.Context, userID int) (*User, error) {
    // 先检查布隆过滤器
    if !filter.TestString(fmt.Sprintf("%d", userID)) {
        return nil, errors.New("用户不存在")
    }
    
    // 查缓存和数据库...
}

缓存击穿(互斥锁)

func GetUserWithLock(ctx context.Context, userID int) (*User, error) {
    key := fmt.Sprintf("user:%d", userID)
    lockKey := fmt.Sprintf("lock:user:%d", userID)
    
    // 1. 查缓存
    val, err := rdb.Get(ctx, key).Result()
    if err == nil {
        var user User
        json.Unmarshal([]byte(val), &user)
        return &user, nil
    }
    
    // 2. 获取锁
    lockValue := uuid.New().String()
    if !TryLock(ctx, lockKey, lockValue, time.Second*3) {
        // 获取锁失败,等待后重试
        time.Sleep(time.Millisecond * 100)
        return GetUserWithLock(ctx, userID)
    }
    defer Unlock(ctx, lockKey, lockValue)
    
    // 3. 双重检查
    val, err = rdb.Get(ctx, key).Result()
    if err == nil {
        var user User
        json.Unmarshal([]byte(val), &user)
        return &user, nil
    }
    
    // 4. 查数据库并写缓存
    user, err := db.GetUserByID(userID)
    if err != nil {
        return nil, err
    }
    
    data, _ := json.Marshal(user)
    rdb.Set(ctx, key, data, time.Hour)
    
    return user, nil
}