Golang笔记之Redis

Golang笔记之Redis

大家好,我是你们的老朋友,一个喜欢折腾技术的博主。今天咱们来聊聊Go语言和Redis这对黄金搭档。Redis作为一款高性能的内存数据库,在缓存、队列、分布式锁等场景下几乎是标配。而Go语言凭借其高并发、简洁的语法,天然适合与Redis配合使用。这篇文章我会从零开始,带你用Golang操作Redis,包括安装、连接、基本操作和几个实用场景,全程代码示例,保证你能跟着跑起来。### 为什么是Go + Redis?先废话两句。Redis是单线程模型,但它速度极快,因为基于内存操作。而Go的goroutine可以轻松创建成千上万个并发任务,配合Redis的响应速度,能扛住极高的QPS。另外,Go的redis库(比如go-redis)封装得非常好,API设计直观,出错处理也清晰。如果你用Python写,虽然也行,但Go的部署就是一个二进制文件,没有依赖,这在生产环境里太香了。### 准备工作:安装依赖在Go项目里,我们最常用的Redis客户端库是github.com/go-redis/redis/v8(注意v8版本对应Go module)。先初始化项目:bashmkdir redis-demo && cd redis-demogo mod init redis-demogo get github.com/go-redis/redis/v8如果你还没装Redis服务器,本地跑一个Docker容器最方便:bashdocker run -d -p 6379:6379 redis:7-alpine### 连接Redis与基础操作先写一个最基础的示例:连接Redis,设置一个键值对,再读取它。注意,go-redis v8版本使用context来控制超时,这点和以前不太一样。gopackage mainimport ( "context" "fmt" "time" "github.com/go-redis/redis/v8")func main() { // 创建一个context,用于控制本次操作的超时时间 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 创建Redis客户端 rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", // Redis地址 Password: "", // 密码,无则留空 DB: 0, // 默认数据库 }) // 测试连接 if err := rdb.Ping(ctx).Err(); err != nil { panic(err) } fmt.Println("连接Redis成功!") // 设置键值对,有效期为10秒 err := rdb.Set(ctx, "name", "博主", 10*time.Second).Err() if err != nil { panic(err) } // 读取键值 val, err := rdb.Get(ctx, "name").Result() if err != nil { panic(err) } fmt.Println("name =", val) // 删除键 rdb.Del(ctx, "name")}这段代码里,Set的第三个参数是过期时间,0表示永久。注意Get返回的是*redis.StringCmd,需要调用.Result()来取值。如果键不存在,会返回redis.Nil错误,你需要判断一下。### 实用场景一:分布式锁Redis最经典的应用之一就是分布式锁。在微服务里,多个实例要互斥访问资源,用Redis的SETNX命令实现最简单。go-redis提供了SetNX方法,结合Expire设置超时,防止死锁。下面是一个完整的分布式锁实现:gopackage mainimport ( "context" "fmt" "time" "github.com/go-redis/redis/v8")// 尝试获取锁func acquireLock(ctx context.Context, rdb *redis.Client, lockKey string, requestID string, ttl time.Duration) (bool, error) { // SETNX:如果键不存在则设置,返回true;否则返回false ok, err := rdb.SetNX(ctx, lockKey, requestID, ttl).Result() if err != nil { return false, err } return ok, nil}// 释放锁(使用Lua脚本保证原子性,只删除自己加的锁)func releaseLock(ctx context.Context, rdb *redis.Client, lockKey string, requestID string) error { // Lua脚本:判断当前值是否为requestID,是则删除 script := ` if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end` _, err := rdb.Eval(ctx, script, []string{lockKey}, requestID).Result() return err}func main() { ctx := context.Background() rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"}) lockKey := "resource:lock" requestID := "instance-1" // 每个请求唯一 // 尝试加锁 ok, err := acquireLock(ctx, rdb, lockKey, requestID, 10*time.Second) if err != nil { panic(err) } if ok { fmt.Println("加锁成功!") // 模拟业务操作 time.Sleep(2 * time.Second) // 释放锁 if err := releaseLock(ctx, rdb, lockKey, requestID); err != nil { panic(err) } fmt.Println("释放锁成功") } else { fmt.Println("加锁失败,其他实例持有锁") }}这里有个关键点:释放锁时不能用简单的Del,因为如果你操作超时导致锁自动过期,而此时另一个请求拿到锁,你直接Del会误删别人的锁。所以用Lua脚本先比较值再删除,保证原子性。这个细节是面试常考点。### 实用场景二:缓存数据库查询结果另一个高频场景是缓存。假设我们有一个用户信息表,查数据库很慢,我们可以把查询结果缓存到Redis。这里用JSON序列化存储,并设置合理的过期时间。gopackage mainimport ( "context" "encoding/json" "fmt" "time" "github.com/go-redis/redis/v8")// 模拟用户结构体type User struct { ID int `json:"id"` Name string `json:"name"` Age int `json:"age"`}// 模拟从数据库查询用户func getUserFromDB(id int) *User { // 假设这里查询耗时100ms time.Sleep(100 * time.Millisecond) return &User{ID: id, Name: "张三", Age: 30}}// 获取用户,优先走缓存func getUser(ctx context.Context, rdb *redis.Client, id int) (*User, error) { cacheKey := fmt.Sprintf("user:%d", id) // 1. 尝试从缓存获取 val, err := rdb.Get(ctx, cacheKey).Result() if err == nil { // 缓存命中,反序列化 user := &User{} if json.Unmarshal([]byte(val), user) == nil { fmt.Println("从缓存获取") return user, nil } } else if err != redis.Nil { return nil, err } // 2. 缓存未命中,查数据库 user := getUserFromDB(id) // 3. 写入缓存,设置5分钟过期 jsonData, _ := json.Marshal(user) if err := rdb.Set(ctx, cacheKey, jsonData, 5*time.Minute).Err(); err != nil { return nil, err } fmt.Println("从数据库获取并写入缓存") return user, nil}func main() { ctx := context.Background() rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"}) // 第一次查询(缓存未命中) u1, _ := getUser(ctx, rdb, 100) fmt.Printf("用户: %+v\n", u1) // 第二次查询(缓存命中) u2, _ := getUser(ctx, rdb, 100) fmt.Printf("用户: %+v\n", u2)}运行后,你会看到第一次打印"从数据库获取",第二次打印"从缓存获取"。这就是典型的Cache-Aside模式。实际项目中,你还可以用Pipeline批量操作,或者用Pub/Sub做消息通知,但今天先讲这两个核心场景。### 注意事项与坑- 连接池go-redis默认有连接池,不用手动管理,但你要注意MaxActiveMaxIdle的配置,防止高并发下连接耗尽。- 错误处理redis.Nil是正常的找不到键的错误,别当成真异常。- 序列化 :不要直接存Go的struct,必须用JSONgob等序列化,否则存进去的是内存地址。- 超时 :生产环境一定要设置DialTimeoutReadTimeout,否则Redis挂了你代码会一直阻塞。### 总结今天我们聊了Go语言操作Redis的基础知识和两个实战场景。从连接客户端、Set/Get入门,到分布式锁的Lua原子脚本,再到缓存数据库查询结果,每一步都有完整的代码示例。Redis本身很强大,但真正用好它,需要在代码层面仔细设计数据结构、过期策略和错误处理。希望这篇文章能帮你少踩一些坑。如果你还想看更多关于Redis的管道、集群、哨兵的内容,欢迎在评论区留言,下期再见!

相关推荐
独行侠影a1 小时前
APScheduler+Redis 分布式定时任务:解决多实例任务重复执行
数据库·redis·分布式
峥无2 小时前
C++11 深度详解:现代 C++ 基石全梳理
开发语言·c++·笔记
阿米亚波2 小时前
【C++ STL】std::unordered_multimap
开发语言·数据结构·c++·笔记·stl
名字还没想好☜3 小时前
Go 的 time.After 在 select 循环里内存泄漏:定时器堆积原理与 timer.Reset 正确姿势
java·数据库·golang·go·goroutine
早睡早起身体好1234 小时前
为什么 Network 中会出现两个同名请求?——CORS 预检请求学习笔记
网络·笔记·学习·网络安全
ttwuai4 小时前
GoFrame 后台日志清空失败:无 WHERE 删除为什么被拦住
前端·golang
@Mike@5 小时前
06-数据库学习笔记(存储模型与数据压缩)
数据库·笔记·学习
进击的程序猿~5 小时前
Go 并发底层原理面试学习指南
开发语言·面试·golang
wdfk_prog6 小时前
嵌入式面试真题学习笔记系列
笔记·学习·面试