记一次redis client配置使用不当造成Proxy CPU负载过高
背景
在服务的缓存中使用了redis作为分布式缓存,在使用的过程中发现通过对比发现了一个异常现象:即redis proxy 的CPU使用率和请求的QPS不符合。和基础设施inf的同事也沟通过后,也没有一个固定的结论(也可能inf同事没有很认真的关注这个问题)
排查过程
现象发现
一次偶然的过程中,发现单个实例redis客户端连接关闭的QPS特别高,已经达到了8~10K左右的QPS, 这个量已经高于对应实例对于redis请求的QPS了。于是就思考为什么客户端有这么频繁的关闭连接,在没有深入排查的时候,可以有两个猜想:
- 客户端连接主动关闭
- 客户端连接被动关闭
而目前redis客户端连接均由连接池管理,也就是连接池中的连接在不停的建立,关闭,建立,关闭~
排查过程
所以,问题就到了,为什么连接池中的连接不停的在管理和建立呢?在此,需要看下使用对应语言的redis client相关的源码实现。项目中使用到的是golang语言的redis-v6客户端
// NewOption by self specified timeouts // default auto load conf unless you disable it by DisableAutoLoadConf() func NewOptionWithTimeout( dialTimeout, // Dial timeout for establishing new connections. readTimeout, // Timeout for socket reads. If reached, commands will fail writeTimeout,// Timeout for socket writes. If reached, commands will fail poolTimeout,// Amount of time client waits for connection if all connections are busy before returning an error idleTimeout,// Amount of time after which client closes idle connections. liveTimeout time.Duration, // Amount of time after which client closes exist connections. poolSize int) *Option { // Maximum number of socket connections. if dialTimeout == 0 { dialTimeout = REDIS_DIAL_TIMEOUT } if readTimeout == 0 { readTimeout = REDIS_READ_TIMEOUT } if writeTimeout == 0 { writeTimeout = REDIS_WRITE_TIMEOUT } if poolTimeout == 0 { poolTimeout = REDIS_POOL_TIMEOUT } if idleTimeout == 0 { idleTimeout = REDIS_IDLE_TIMEOUT } if poolSize <= 0 { poolSize = REDIS_POOL_SIZE } opts := &redis.Options{ DialTimeout: dialTimeout, ReadTimeout: readTimeout, WriteTimeout: writeTimeout, PoolSize: poolSize, PoolTimeout: poolTimeout, IdleTimeout: idleTimeout, LiveTimeout: liveTimeout, IdleCheckFrequency: REDIS_IDLE_CHECK_FREQUENCY, } opt := &Option{ Options: opts, PoolInitSize: REDIS_POOL_INIT_SIZE, autoLoadConf: REDIS_AUTO_LOAD_CONF, autoLoadInterval: REDIS_AUTO_LOAD_INTERVAL, maxFailureRate: MAX_FAILURE_RATE, minSample: MIN_SAMPLE, windowTime: WINDOW_TIME, configFilePath: "", useConsul: true, } return opt }
查看源码之后,PoolTimeout会影响客户端的错误率,但是不会影响连接的生存时间。liveTimeout和IdleTimeout会影响连接池中一条链接的的生存时间。再看两个参数值,发现liveTimeout使用的是默认值5min,而IdleTimeout使用的居然是和ReadTimeout相同的200ms,也就是说,一条链接如果空闲超过200ms,则会被关闭。所以综合分析来看,可能是两点原因导致了这种情况:
- redis-v6的连接池使用的是固定连接池的方式,也就是一旦新建,就默认值池子里必须有这么多连接。不是像Java 中redis client 的连接池配置,拥有最大空闲连接,以及最小空闲连接等配置,来保证连接池保持在一个合理的水平。这样导致即使连接池中,有大量空闲连接的情况下,也不会被销毁。
- IdleTimeout的参数值的错误使用,导致了大量空闲的链接在不停的销毁重建,从而加重了proxy CPU的负担
修复
将IdleTimeout 配置参数从200ms改到2h之后,测试后发现明显可以降低客户端连接关闭的QPS。 然后进行上线
1. 客户端连接关闭QPS明显降低
2. redis proxy CPU 使用率下降了一倍以上
总结
以上仅个人观点,欢迎批评指正