更多个人笔记:(仅供参考,非盈利)
gitee: https://gitee.com/harryhack/it_note
github: https://github.com/ZHLOVEYY/IT_note
安装redis客户端:go get github.com/redis/go-redis/v9
注意go mod tidy的时候不要import成了"github.com/go-redis/redis" 需要检查一下
基础操作,连接redis和CRUD
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default
//password和db如果都是默认值可以不设置,这里展示一下
})
ctx := context.Background() //创建上下文
//测试连接
_,err := client.Ping(ctx).Result()
if err != nil {
log.Fatal("连接失败",err)
}
fmt.Println("连接成功")
///设置键值对
err = client.Set(ctx,"user1","Alice",0).Err() //0表示永不过期
if err!=nil {
log.Fatal("设置键值对失败",err)
}
fmt.Println("设置了user1=Alice")
//获取键值对
name,err := client.Get(ctx,"user1").Result()
if err!=nil {
log.Fatal("获取键值对失败",err)
}
fmt.Println("user1的值是",name)
//设置过期时间(10秒)
err = client.SetEx(ctx,"session1","123456",10*time.Second).Err()
if err!=nil {
log.Fatal("设置过期时间失败",err)
}
fmt.Println("设置了session1=123456,过期时间为10秒")
//等待2秒
fmt.Println("等待2秒...")
time.Sleep(2*time.Second)
// 获取 session 的剩余时间
ttl, err := client.TTL(ctx, "session1").Result()
if err != nil {
log.Fatal("获取过期时间失败", err)
}
fmt.Printf("session1 剩余时间: %v\n", ttl)
//删除键
_,err = client.Del(ctx,"user1").Result()
if err!=nil {
log.Fatal("删除键失败",err)
}
fmt.Println("删除了user1")
//再次获取键值对
name, err = client.Get(ctx, "user1").Result()
if err == redis.Nil { //如果不存在会返回redis.Nil
fmt.Println("user1 不存在")
} else if err != nil {
log.Fatal("获取键值对失败", err)
} else {
fmt.Printf("user1的值是: %s\n", name)
}
}
- 注意需要传入ctx上下文
- 注意最后有.Result()或者.Err()的特点
- 主题语法和Redis其实是比较像的
操作复杂数据结构(Hash 和 List)
package main
import (
"context"
"fmt"
"log"
"github.com/redis/go-redis/v9"
)
func main() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
ctx := context.Background() //创建上下文
//操作Hash(存储用户对象)
user := map[string]interface{}{ //interface{} 表示任意类型,就很方便
"name": "Bob",
"age": 30,
}
err := client.HSet(ctx, "user2", user).Err()
if err != nil {
log.Fatal("Hset失败", err)
}
fmt.Println("存储用户user2")
//获取Hash
name, err := client.HGet(ctx, "user2", "name").Result()
if err != nil {
log.Fatal("Hget失败", err)
}
fmt.Printf("获取用户user2的name:%s\n", name)
//操作List(消息队列)
queue := "message_queue"
err = client.RPush(ctx, queue, "msg1", "msg2", "msg3").Err() //Rpush从列表右端(尾部)插入元素,和Lpush相反
if err != nil {
log.Fatal("RPush失败", err)
}
fmt.Println("消息入队到message_queue")
//弹出
msg, err := client.LPop(ctx, queue).Result()
if err != nil {
log.Fatal("LPop失败", err)
}
fmt.Printf("从message_queue弹出消息:%s\n", msg)
}
(一些设置要是输出怪可能是和已经存在的变量名有关,改一下就可以了)
- Hash:HSet 和 HGet 适合存储结构化数据(如用户信息)。
- List:RPush 和 LPop 实现 FIFO 队列,常用作任务队列。
- 实际项目中,Hash 常用于缓存对象,List 用于异步任务处理
发布/订阅(Pub/Sub)
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
ctx := context.Background() //创建上下文
//订阅者
go func(){ //通过协程持续监听
pubsub := client.Subscribe(ctx,"channel1")
defer pubsub.Close()
for msg := range pubsub.Channel(){ //调用channel方法,获取消息
fmt.Printf("收到消息:%s(频道:%s)\n",msg.Payload,msg.Channel)
}
}()
//发布者
time.Sleep(1*time.Second) //确保订阅者启动
err := client.Publish(ctx,"channel1","hello,Redis").Err()
if err != nil{
log.Fatal("发布失败",err)
}
fmt.Println("发布消息成功")
//保持运行
time.Sleep(2 * time.Second)
}
分布式锁
通过lua脚本等简单了解分布式锁的概念
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
func acquireLock(client *redis.Client, ctx context.Context, lockKey string, value string, ttl time.Duration) bool {
// 使用 SETNX 命令尝试获取锁
result, err := client.SetNX(ctx, lockKey, value, ttl).Result()
if err != nil {
log.Println("Failed to acquire lock:", err)
return false
}
return result
//和SetNX返回值有关,redis中当键不存在时,设置成功,返回 1
//SetNX:set if not exist
}
func releaseLock(client *redis.Client, ctx context.Context, lockKey, value string) bool {
// Lua 脚本确保原子性释放
script := `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
` // KEYS[1] 是锁的键,ARGV[1] 是锁的值,检查锁的值是否匹配,如果匹配则删除锁
result, err := client.Eval(ctx, script, []string{lockKey}, value).Int() //[]string{lockKey} 是 Lua 脚本的参数,value 是锁的值
if err != nil {
return false
}
return result == 1
}
func main() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
ctx := context.Background()
lockKey := "mylock"
value := "unique_123"
ttl := 20 * time.Second
if acquireLock(client, ctx, lockKey, value, ttl) {
fmt.Println("获取锁成功")
// 模拟业务操作
time.Sleep(2 * time.Second)
//释放锁
if releaseLock(client, ctx, lockKey, value) {
fmt.Println("释放锁成功")
} else {
fmt.Println("释放锁失败")
}
}else {
fmt.Println("获取锁失败")
}
}
- 关键在于做到只能释放自己的锁,防止竞争
- 可以用于库存扣减、分布式任务调度
连接池和性能优化
简单了解
包括通过pipeline进行批量输入
package main
import (
"context"
"fmt"
"log"
"sync"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
PoolSize: 10,
MinIdleConns: 2,
MaxRetries: 3,
DialTimeout: time.Second * 5,
ReadTimeout: time.Second * 3,
WriteTimeout: time.Second * 3,
})
ctx := context.Background()
// 批量写入(使用 Pipeline 提升性能)
pipe := client.Pipeline()
for i := 0; i < 100; i++ { //将命令加入到管道中,但不立即执行
pipe.Set(ctx, fmt.Sprintf("key:%d", i), i, time.Second*60)
//sprinf形成新的变量比如key:0,key:1,key:2
}
_, err := pipe.Exec(ctx) //一次性执行所有命令
if err != nil {
log.Fatal("Pipeline 执行失败:", err)
}
fmt.Println("批量写入 100 个键完成")
// 并发读取
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1) // 增加等待组中的计数
go func(id int) {
defer wg.Add(-1)
val, err := client.Get(ctx, fmt.Sprintf("key:%d", i)).Result()
if err != nil {
log.Printf("读取 key:%d 失败: %v", id, err)
return
}
fmt.Printf("读取 key:%d = %s\n", id, val)
}(i)
}
wg.Wait() // 等待所有协程完成
}
- 接池:PoolSize 和 MinIdleConns 控制连接复用,适合高并发。
- Pipeline:批量执行命令,减少网络开销。
- 并发:使用 goroutine 并发操作,结合 sync.WaitGroup 同步。
- 超时配置:防止网络问题导致阻塞。