news 2026/8/17 3:45:06

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 timeout,QPS一上来Redis就扛不住。我翻了半天代码,连接池配置是默认值,PoolSize才是10*runtime.GOMAXPROCS,高并发场景下根本不够用。

我直接把PoolSize调到200,MinIdleConns设成50,重新部署后超时消失。go-redis用起来简单,但连接池参数不调好,生产环境迟早出事。这篇我从安装配置讲起,把go-redis v9的基本操作和连接池调优一次性讲透。

一、go-redis安装与连接配置

go-redis是Redis的Go客户端,v9版本做了大量重构,API和v8有区别。

packagemainimport("context""fmt""time""github.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与Hash

String存缓存值和计数器,Hash适合存对象。

// String类型操作funcstringOps(ctx context.Context,rdb*redis.Client){// 设置键值,带过期时间rdb.Set(ctx,"name","张三",10*time.Minute)// 获取值,区分键不存在和查询出错val,err:=rdb.Get(ctx,"name").Result()iferr==redis.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","lisi@example.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与ZSet

List做队列,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",i+1,z.Member,z.Score)}// 增加分数rdb.ZIncrBy(ctx,"rank:score",10,"p2")}

三、连接池参数调优

连接池是go-redis性能的关键,参数配错要么连接不够用要么资源浪费。

packagemainimport("context""fmt""sync""time""github.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,// 池满时等待时间,默认ReadTimeout+1sIdleTimeout:5*time.Minute,// 空闲连接存活时间MaxRetries:3,// 网络错误自动重试次数})deferrdb.Close()ctx:=context.Background()// 并发测试连接池varwg sync.WaitGroupfori:=0;i<100;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调到200,MinIdleConns设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();err==nil{result[id]=val}}returnresult}

调完后连接池等待次数从每天几万次降到几十次。经验就是连接池参数一定要根据实际QPS调,别用默认值。

五、对比分析

特性go-redis v9redigo
API风格链式调用,类型安全手动写命令字符串
连接池内置,参数丰富需手动配置Pool结构
集群支持原生Cluster和Sentinel需第三方或自己实现
Pipeline内置Pipeline和TxPipeline需手动管理conn.Send
自动重试内置MaxRetries需自己实现
Context原生支持后期版本才加
社区维护活跃,官方推荐维护趋缓

新项目建议直接选go-redis v9,API更现代,类型安全避免手写命令出错,集群和哨兵开箱即用。redigo轻量但连接管理和错误处理要写更多代码。

总结与预告

go-redis v9上手简单,五种数据类型的API设计清晰。连接池是生产环境关键,PoolSize和MinIdleConns一定要根据QPS调优。Pipeline能批量操作就别循环单条查,省连接省网络。

下一篇讲Redis进阶,深入Pipeline原理、事务的WATCH机制和发布订阅模式的实现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/17 3:36:13

文华财经指标公式MT4指标MT5指标

HH:HHV(HIGH,10); LL:LLV(LOW,10); HH1:BARSLAST((HH>REF(HH,1))); LL1:BARSLAST((LL < REF(LL,1))); DRAWTEXT(CROSS(HH1,LL1),90,众),COLORWHITE; DRAWTEXT(CROSS(LL1,HH1),90,4),COLORGREEN; DRAWTEXT(CROSS(HH1,LL1),60,龙),COLORWHITE; DRAWTEXT(CROSS(LL1,HH1),60,…

作者头像 李华
网站建设 2026/8/17 3:28:29

NumPy np.c_ 与 np.r_ 函数:数据拼接与特征工程的简洁利器

1. 从两个不起眼的拼接函数说起如果你用过NumPy&#xff0c;大概率对np.concatenate、np.stack、np.hstack这些数组拼接函数不陌生。它们功能强大&#xff0c;但写起来有时候确实有点“啰嗦”。今天想聊的是两个非常“懒人”的工具&#xff1a;np.c_和np.r_。我第一次在别人的代…

作者头像 李华
网站建设 2026/8/17 3:25:47

生物信息学分析的可重复性实践与Git进阶应用

1. 生物信息学研究的可重复性危机在生物信息学领域&#xff0c;我们正面临着一个严峻的现实&#xff1a;超过70%的已发表研究成果无法被其他研究团队成功复现。这个数字来自《自然》杂志2021年的一项调查&#xff0c;它揭示了生物信息学分析中普遍存在的可重复性问题。作为一名…

作者头像 李华
网站建设 2026/8/17 3:21:22

Windows重启后锁屏状态自动登录:原理、方案与安全实践

1. 项目缘起&#xff1a;一个看似矛盾但真实存在的需求“Windows重启后保持锁屏状态下自动登录”&#xff0c;这个标题乍一看有点拧巴。锁屏和自动登录&#xff0c;听起来像是两个互斥的功能&#xff1a;一个是为了安全&#xff0c;防止未经授权的访问&#xff1b;另一个是为了…

作者头像 李华