前言
用go开发web项目时,经常会用到redis,redis有名的包有go-redis和redigo。redigo是官方推荐。https://github.com/gomodule/redigo
聊聊redis的i/o并发
Redis is single-threaded with epoll/kqueue and scales indefinitely in terms of I/O concurrency.
redis是单线程的,但又是处实现并发的呢?参照地址:
https://stackoverflow.com/questions/10489298/redis-is-single-threaded-then-how-does-it-do-concurrent-i-o
redis是使用事件循环来实现并发的,事件是原子性的,还没有额外的锁开销。设计非常精妙。
我们在读写redis时,大多都是网络传输层的开销,redis计算是非常快的。所以我们尽量用多个连接去读写redis,相当于并发做网络传递,排队等着redis计算,不能让redis计算引擎闲下来。
使用连接池
安装redigo包:
1 |
go get github.com/gomodule/redigo/redis |
在使用redigo的时候,强烈建议使用连接池,不然每次都得tcp建链,不嫌麻烦吗。而使用连接池的话,只管去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 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package redisClient import ( "github.com/gomodule/redigo/redis" "time" ) var redisClient *redis.Pool func GetRedis() *redis.Pool { return redisClient } func init() { // 建立连接池 redisClient = &redis.Pool{ MaxIdle: 10, //最大空闲连接数,即会有这么多个连接提前等待着,但过了超时时间也会关闭。 MaxActive: 10000, // 最大连接数,即最多的tcp连接数,一般建议往大的配置,但不要超过操作系统文件句柄个数(centos下可以ulimit -n查看)。 IdleTimeout: 5 * time.Second, // 空闲连接超时时间,但应该设置比redis服务器超时时间短。否则服务端超时了,客户端保持着连接也没用。 Wait: true, Dial: func() (redis.Conn, error) { con, err := redis.Dial("tcp", "192.168.123.134:6379", redis.DialPassword("10086"), redis.DialDatabase(1), redis.DialConnectTimeout(70 * time.Second), redis.DialReadTimeout(70 * time.Second), redis.DialWriteTimeout(70 * time.Second)) if err != nil { return nil, err } return con, nil }, } // 从池里获取连接rc := RedisClient.Get() // 用完后将连接放回连接池defer rc.Close() // 错误判断if conn.Err() != nil { } } |
其它有几个需要注意的地方:
- MaxActive 最大连接数,即最多的tcp连接数,一般建议往大的配置,但不要超过操作系统文件句柄个数(centos下可以ulimit -n查看)。
- MaxIdle 最大空闲连接数,即会有这么多个连接提前等待着,但过了超时时间也会关闭。
- IdleTimeout 空闲连接超时时间,但应该设置比redis服务器超时时间短。否则服务端超时了,客户端保持着连接也没用。
- Wait 这是个很有用的配置。好多东抄抄本抄抄的文章都没有提。如果超过最大连接,是报错,还是等待。
使用(iris)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var pool = redisClient.GetRedis() // 获取redis连接池 func (user *userController)Get(ctx iris.Context) { rdb := pool.Get() // 从redis连接池获取一个连接 defer rdb.Close() // 归还连接到连接池 // rdb.Do("Get", "user") 执行redis命令 // redis.String() 类型转换 data, err := redis.String(rdb.Do("Get", "user")) if err!=nil { fmt.Println(err.Error()) return } ctx.JSON(iris.Map{ "status": 0, "message": "success", "data":data, }) } |
若value的类型为int,则用redis.Int转换
若value的类型为string,则用redis.String转换
若value的类型为json,则用redis.Byte转换
常见报错
连接池消耗殆尽
1 |
redigo: connection pool exhausted |
redigo常常会有这样的报错。我们来从redigo源码上来分析这个问题。
1 2 3 4 5 |
// Handle limit for p.Wait == false. if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { p.mu.Unlock() return nil, ErrPoolExhausted } |
当Wait==false,并且当前有效连接>=最大连接数里就报这个错了。要解决这个问题的话,可以修改这个参数:
- MaxActive 可以把MaxActive调大(一般设置为500,1000问题都不大。)但如果redis服务器负载已经很高了(可以看redis-server CPU占用),去调大MaxActive就没多大意义。还是需要根据实际情况来权衡。
- Wait 可以把Wait设置为true。wait的话必然会加大响应,如果对响应时间要求较高的话,还得从别的途径来解决。
- 还可以加从库通过读写分离来解决。特别是PHP,没有常驻的redis连接池,建议增加多个从库。