Go语言操作Redis
Redis介绍
Redis是一个开源的内存数据库,Redis提供了多种不同类型的数据结构,很多业务场景下的问题都可以很自然地映射到这些数据结构上。除此之外,通过复制、持久化和客户端分片等特性,我们可以很方便地将Redis扩展成一个能够包含数百GB数据、每秒处理上百万次请求的系统。
Redis支持的数据结构
Redis支持诸如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、带范围查询的排序集合(sorted sets)、位图(bitmaps)、hyperloglogs、带半径查询和流的地理空间索引等数据结构(geospatial indexes)。
Redis应用场景
- 缓存系统,减轻主数据库(MySQL)的压力。
- 计数场景,比如微博、抖音中的关注数和粉丝数。
- 热门排行榜,需要排序的场景特别适合使用ZSET。
- 利用LIST可以实现队列的功能。
准备Redis环境
这里直接使用Docker启动一个redis环境,方便学习使用。
docker启动一个名为redis507的5.0.7版本的redis server示例:
1 | docker run --name redis507 -p 6379:6379 -d redis:5.0.7 |
注意:此处的版本、容器名和端口号请根据自己需要设置。
启动一个redis-cli连接上面的redis server:
1 | docker run -it --network host --rm redis:5.0.7 redis-cli |
go-redis库
安装
区别于另一个比较常用的Go语言redis client库:redigo,我们这里采用https://github.com/go-redis/redis连接Redis数据库并进行操作,因为go-redis
支持连接哨兵及集群模式的Redis。
使用以下命令下载并安装:
1 | go get -u github.com/ go -redis/redis |
连接
普通连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 声明一个全局的rdb变量 var rdb *redis.Client // 初始化连接 func initClient() (err error) { rdb = redis.NewClient(&redis.Options{ Addr: "localhost:6379" , Password: "" , // no password set DB: 0, // use default DB }) _, err = rdb.Ping().Result() if err != nil { return err } return nil } |
连接Redis哨兵模式
1 2 3 4 5 6 7 8 9 10 11 | func initClient()(err error){ rdb := redis.NewFailoverClient(&redis.FailoverOptions{ MasterName: "master" , SentinelAddrs: []string{ "x.x.x.x:26379" , "xx.xx.xx.xx:26379" , "xxx.xxx.xxx.xxx:26379" }, }) _, err = rdb.Ping().Result() if err != nil { return err } return nil } |
连接Redis集群
1 2 3 4 5 6 7 8 9 10 | func initClient()(err error){ rdb := redis.NewClusterClient(&redis.ClusterOptions{ Addrs: []string{ ":7000" , ":7001" , ":7002" , ":7003" , ":7004" , ":7005" }, }) _, err = rdb.Ping().Result() if err != nil { return err } return nil } |
基本使用
set/get示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | func redisExample() { err := rdb.Set( "score" , 100, 0).Err() if err != nil { fmt.Printf( "set score failed, err:%v\n" , err) return } val, err := rdb.Get( "score" ).Result() if err != nil { fmt.Printf( "get score failed, err:%v\n" , err) return } fmt.Println( "score" , val) val2, err := rdb.Get( "name" ).Result() if err == redis.Nil { fmt.Println( "name does not exist" ) } else if err != nil { fmt.Printf( "get name failed, err:%v\n" , err) return } else { fmt.Println( "name" , val2) } } |
zset示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | func redisExample2() { zsetKey := "language_rank" languages := []redis.Z{ redis.Z{Score: 90.0, Member: "Golang" }, redis.Z{Score: 98.0, Member: "Java" }, redis.Z{Score: 95.0, Member: "Python" }, redis.Z{Score: 97.0, Member: "JavaScript" }, redis.Z{Score: 99.0, Member: "C/C++" }, } // ZADD num, err := rdb.ZAdd(zsetKey, languages...).Result() if err != nil { fmt.Printf( "zadd failed, err:%v\n" , err) return } fmt.Printf( "zadd %d succ.\n" , num) // 把Golang的分数加10 newScore, err := rdb.ZIncrBy(zsetKey, 10.0, "Golang" ).Result() if err != nil { fmt.Printf( "zincrby failed, err:%v\n" , err) return } fmt.Printf( "Golang's score is %f now.\n" , newScore) // 取分数最高的3个 ret, err := rdb.ZRevRangeWithScores(zsetKey, 0, 2).Result() if err != nil { fmt.Printf( "zrevrange failed, err:%v\n" , err) return } for _, z := range ret { fmt.Println(z.Member, z.Score) } // 取95~100分的 op := redis.ZRangeBy{ Min: "95" , Max: "100" , } ret, err = rdb.ZRangeByScoreWithScores(zsetKey, op).Result() if err != nil { fmt.Printf( "zrangebyscore failed, err:%v\n" , err) return } for _, z := range ret { fmt.Println(z.Member, z.Score) } } |
输出结果如下:
1 2 3 4 5 6 7 8 9 10 | $ ./06redis_demo zadd 0 succ. Golang's score is 100.000000 now. Golang 100 C/C++ 99 Java 98 JavaScript 97 Java 98 C/C++ 99 Golang 100 |
Pipeline
Pipeline
主要是一种网络优化。它本质上意味着客户端缓冲一堆命令并一次性将它们发送到服务器。这些命令不能保证在事务中执行。这样做的好处是节省了每个命令的网络往返时间(RTT)。
Pipeline
基本示例如下:
1 2 3 4 5 6 7 | pipe := rdb.Pipeline() incr := pipe.Incr( "pipeline_counter" ) pipe.Expire( "pipeline_counter" , time.Hour) _, err := pipe.Exec() fmt.Println(incr.Val(), err) |
上面的代码相当于将以下两个命令一次发给redis server端执行,与不使用Pipeline
相比能减少一次RTT。
1 2 | INCR pipeline_counter EXPIRE pipeline_counts 3600 |
也可以使用Pipelined
:
1 2 3 4 5 6 7 | var incr *redis.IntCmd _, err := rdb.Pipelined( func (pipe redis.Pipeliner) error { incr = pipe.Incr( "pipelined_counter" ) pipe.Expire( "pipelined_counter" , time.Hour) return nil }) fmt.Println(incr.Val(), err) |
在某些场景下,当我们有多条命令要执行时,就可以考虑使用pipeline来优化。
事务
Redis是单线程的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如在它们之间交替执行。但是,Multi/exec
能够确保在multi/exec
两个语句之间的命令之间没有其他客户端正在执行命令。
在这种场景我们需要使用TxPipeline
。TxPipeline
总体上类似于上面的Pipeline
,但是它内部会使用MULTI/EXEC
包裹排队的命令。例如:
1 2 3 4 5 6 7 | pipe := rdb.TxPipeline() incr := pipe.Incr( "tx_pipeline_counter" ) pipe.Expire( "tx_pipeline_counter" , time.Hour) _, err := pipe.Exec() fmt.Println(incr.Val(), err) |
上面代码相当于在一个RTT下执行了下面的redis命令:
1 2 3 4 | MULTI INCR pipeline_counter EXPIRE pipeline_counts 3600 EXEC |
还有一个与上文类似的TxPipelined
方法,使用方法如下:
1 2 3 4 5 6 7 | var incr *redis.IntCmd _, err := rdb.TxPipelined( func (pipe redis.Pipeliner) error { incr = pipe.Incr( "tx_pipelined_counter" ) pipe.Expire( "tx_pipelined_counter" , time.Hour) return nil }) fmt.Println(incr.Val(), err) |
Watch
在某些场景下,我们除了要使用MULTI/EXEC
命令外,还需要配合使用WATCH
命令。在用户使用WATCH
命令监视某个键之后,直到该用户执行EXEC
命令的这段时间里,如果有其他用户抢先对被监视的键进行了替换、更新、删除等操作,那么当用户尝试执行EXEC
的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试事务或者放弃事务。
1 | Watch(fn func (*Tx) error, keys ...string) error |
Watch方法接收一个函数和一个或多个key作为参数。基本使用示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 监视watch_count的值,并在值不变的前提下将其值+1 key := "watch_count" err = client.Watch( func (tx *redis.Tx) error { n, err := tx.Get(key).Int() if err != nil && err != redis.Nil { return err } _, err = tx.Pipelined( func (pipe redis.Pipeliner) error { pipe.Set(key, n+1, 0) return nil }) return err }, key) |
最后看一个官方文档中使用GET和SET命令以事务方式递增Key的值的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | const routineCount = 100 increment := func (key string) error { txf := func (tx *redis.Tx) error { // 获得当前值或零值 n, err := tx.Get(key).Int() if err != nil && err != redis.Nil { return err } // 实际操作(乐观锁定中的本地操作) n++ // 仅在监视的Key保持不变的情况下运行 _, err = tx.Pipelined( func (pipe redis.Pipeliner) error { // pipe 处理错误情况 pipe.Set(key, n, 0) return nil }) return err } for retries := routineCount; retries > 0; retries-- { err := rdb.Watch(txf, key) if err != redis.TxFailedErr { return err } // 乐观锁丢失 } return errors.New( "increment reached maximum number of retries" ) } var wg sync.WaitGroup wg.Add(routineCount) for i := 0; i < routineCount; i++ { go func () { defer wg.Done() if err := increment( "counter3" ); err != nil { fmt.Println( "increment error:" , err) } }() } wg.Wait() n, err := rdb.Get( "counter3" ).Int() fmt.Println( "ended with" , n, err) |
更多详情请查阅文档。
封装使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package sql_redis import ( "fmt" "github.com/go-redis/redis" "time" ) var RedisDb *redis.Client func init() { // 连接redis RedisDb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379" , }) _, err := RedisDb.Ping().Result() if err != nil { fmt.Println( "未成功连接Redis:" , err) return } println( "连接Redis成功" ) } func RedisSet(key string, value interface {}, expiration time.Duration) ( interface {},error) { err := RedisDb.Set(key, value, expiration).Err() if err != nil { return fmt.Sprintln( "Redis设置指失败" ),err } return fmt.Sprintln( "设置成功" ),nil } func RedisGet(key string) ( interface {}, error) { value, err := RedisDb.Get(key).Result() if err != nil { fmt.Println( "Redis获取key失败" ) return nil, err } return value, nil } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)