Redis入门:go-redis基本操作与连接池

📅 发布时间:2026/8/17 3:45:16
Redis入门:go-redis基本操作与连接池 Redis入门:go-redis基本操作与连接池摘要: 本篇讲解Go操作Redis的核心库go-redis v9包括客户端连接配置、String/Hash/List/Set/ZSet五种数据类型的基本操作、连接池参数调优(PoolSize、MinIdleConns、PoolTimeout)分享连接池耗尽导致请求超时的踩坑经历对比go-redis与redigo的优劣。开篇故事上周五晚上我们一个营销活动的接口开始报超时。日志里全是redis: connection pool timeoutQPS一上来Redis就扛不住。我翻了半天代码连接池配置是默认值PoolSize才是10*runtime.GOMAXPROCS高并发场景下根本不够用。我直接把PoolSize调到200MinIdleConns设成50重新部署后超时消失。go-redis用起来简单但连接池参数不调好生产环境迟早出事。这篇我从安装配置讲起把go-redis v9的基本操作和连接池调优一次性讲透。一、go-redis安装与连接配置go-redis是Redis的Go客户端v9版本做了大量重构API和v8有区别。packagemainimport(contextfmttimegithub.com/redis/go-redis/v9)// 创建Redis客户端配置连接池参数funcnewRedisClient()*redis.Client{rdb:redis.NewClient(redis.Options{Addr:localhost:6379,// Redis地址Password:,// 密码没设就留空DB:0,// 使用的数据库编号PoolSize:200,// 连接池大小默认10*GOMAXPROCSMinIdleConns:20,// 最小空闲连接保持预热PoolTimeout:5*time.Second,// 从池中获取连接的超时IdleTimeout:5*time.Minute,// 空闲连接超时DialTimeout:5*time.Second,// 建立连接超时ReadTimeout:3*time.Second,// 读操作超时MaxRetries:3,// 命令失败重试次数})returnrdb}funcmain(){rdb:newRedisClient()deferrdb.Close()// Ping测试连接ctx:context.Background()pong,err:rdb.Ping(ctx).Result()iferr!nil{fmt.Println(连接失败:,err)return}fmt.Println(连接成功:,pong)// 输出PONG}二、五种数据类型基本操作String与HashString存缓存值和计数器Hash适合存对象。// String类型操作funcstringOps(ctx context.Context,rdb*redis.Client){// 设置键值带过期时间rdb.Set(ctx,name,张三,10*time.Minute)// 获取值区分键不存在和查询出错val,err:rdb.Get(ctx,name).Result()iferrredis.Nil{fmt.Println(键不存在)}elseiferr!nil{fmt.Println(查询出错:,err)}else{fmt.Println(name ,val)}// 自增操作适合做计数器rdb.Set(ctx,counter,0,0)rdb.Incr(ctx,counter)// 1rdb.IncrBy(ctx,counter,5)// 5rdb.Decr(ctx,counter)// -1// SetNX:不存在才设置常用于分布式锁ok,_:rdb.SetNX(ctx,lock:order:1,holder,10*time.Second).Result()fmt.Println(获取锁:,ok)}// Hash类型操作funchashOps(ctx context.Context,rdb*redis.Client){// 一个key下存多个字段rdb.HSet(ctx,user:1,name,李四,age,28,email,lisiexample.com)// 获取单个字段name,_:rdb.HGet(ctx,user:1,name).Result()fmt.Println(name ,name)// 获取所有字段fields,_:rdb.HGetAll(ctx,user:1).Result()fork,v:rangefields{fmt.Printf(%s %s\n,k,v)}rdb.HIncrBy(ctx,user:1,age,1)// 字段自增rdb.HDel(ctx,user:1,email)// 删除字段}List、Set与ZSetList做队列Set做标签去重ZSet做排行榜。// List类型:双向链表适合消息队列funclistOps(ctx context.Context,rdb*redis.Client){rdb.LPush(ctx,messages,msg1,msg2,msg3)// 左推入msg,_:rdb.RPop(ctx,messages).Result()// 右弹出先进先出fmt.Println(弹出:,msg)// 获取范围元素0到-1表示全部vals,_:rdb.LRange(ctx,messages,0,-1).Result()fmt.Println(剩余消息:,vals)// 阻塞式弹出适合消费队列result,_:rdb.BRPop(ctx,5*time.Second,messages).Result()iflen(result)0{fmt.Println(阻塞弹出:,result[1])}}// Set类型:无序集合自动去重funcsetOps(ctx context.Context,rdb*redis.Client){rdb.SAdd(ctx,tags:1,go,redis,backend)rdb.SAdd(ctx,tags:2,go,database,backend)// 交集:共同标签inter,_:rdb.SInter(ctx,tags:1,tags:2).Result()fmt.Println(共同标签:,inter)// [go backend]// 差集:tags:1有但tags:2没有的diff,_:rdb.SDiff(ctx,tags:1,tags:2).Result()fmt.Println(差集:,diff)// [redis]}// ZSet类型:有序集合带分数适合排行榜funczsetOps(ctx context.Context,rdb*redis.Client){rdb.ZAdd(ctx,rank:score,redis.Z{Score:100,Member:p1},redis.Z{Score:85,Member:p2},redis.Z{Score:92,Member:p3})// 按分数从高到低取前10名rank,_:rdb.ZRevRangeWithScores(ctx,rank:score,0,9).Result()fori,z:rangerank{fmt.Printf(第%d名: %s 分数:%.0f\n,i1,z.Member,z.Score)}// 增加分数rdb.ZIncrBy(ctx,rank:score,10,p2)}三、连接池参数调优连接池是go-redis性能的关键参数配错要么连接不够用要么资源浪费。packagemainimport(contextfmtsynctimegithub.com/redis/go-redis/v9)funcmain(){rdb:redis.NewClient(redis.Options{Addr:localhost:6379,PoolSize:200,// 连接池上限默认10*GOMAXPROCSMinIdleConns:20,// 预热连接数建议PoolSize的10%-25%PoolTimeout:4*time.Second,// 池满时等待时间默认ReadTimeout1sIdleTimeout:5*time.Minute,// 空闲连接存活时间MaxRetries:3,// 网络错误自动重试次数})deferrdb.Close()ctx:context.Background()// 并发测试连接池varwg sync.WaitGroupfori:0;i100;i{wg.Add(1)gofunc(nint){deferwg.Done()rdb.Set(ctx,fmt.Sprintf(key:%d,n),value,10*time.Second)}(i)}wg.Wait()// 查看连接池状态stats:rdb.PoolStats()fmt.Printf(总连接: %d, 空闲: %d, 等待次数: %d\n,stats.TotalConns,stats.IdleConns,stats.Waits)}调优看三个指标。stats.Waits多说明PoolSize太小。stats.IdleConns常年接近PoolSize说明配多了。stats.WaitDuration长说明要么加PoolSize要么查Redis本身是否慢。四、独家踩坑:连接池耗尽导致超时上线后发现高峰期接口偶尔超时报错redis: connection pool exhausted。第一反应是Redis扛不住了去看监控CPU才20%。然后看应用日志发现超时集中在一个批量查询接口循环里每次调一次Redis中间还套了time.Sleep(2 * time.Second)。// 问题代码:循环单条查询连接池被打满funcbadExample(ctx context.Context,rdb*redis.Client,ids[]int64){for_,id:rangeids{// 每次Get从池中借一个连接val,_:rdb.Get(ctx,fmt.Sprintf(user:%d,id)).Result()time.Sleep(2*time.Second)// 耗时操作期间连接被占用_val}}真正的问题在PoolSize默认值太小。线上GOMAXPROCS是8默认PoolSize才80高并发下很快借光。修复分两步PoolSize调到200MinIdleConns设50。把循环查询改成Pipeline批量查。// 修复后:用Pipeline批量查询只占一个连接funcfixedExample(ctx context.Context,rdb*redis.Client,ids[]int64)map[int64]string{pipe:rdb.Pipeline()cmds:make(map[int64]*redis.StringCmd,len(ids))for_,id:rangeids{cmds[id]pipe.Get(ctx,fmt.Sprintf(user:%d,id))// 注册命令}pipe.Exec(ctx)// 一次性执行result:make(map[int64]string,len(ids))forid,cmd:rangecmds{ifval,err:cmd.Result();errnil{result[id]val}}returnresult}调完后连接池等待次数从每天几万次降到几十次。经验就是连接池参数一定要根据实际QPS调别用默认值。五、对比分析特性go-redis v9redigoAPI风格链式调用类型安全手动写命令字符串连接池内置参数丰富需手动配置Pool结构集群支持原生Cluster和Sentinel需第三方或自己实现Pipeline内置Pipeline和TxPipeline需手动管理conn.Send自动重试内置MaxRetries需自己实现Context原生支持后期版本才加社区维护活跃官方推荐维护趋缓新项目建议直接选go-redis v9API更现代类型安全避免手写命令出错集群和哨兵开箱即用。redigo轻量但连接管理和错误处理要写更多代码。总结与预告go-redis v9上手简单五种数据类型的API设计清晰。连接池是生产环境关键PoolSize和MinIdleConns一定要根据QPS调优。Pipeline能批量操作就别循环单条查省连接省网络。下一篇讲Redis进阶深入Pipeline原理、事务的WATCH机制和发布订阅模式的实现。