记一次redis client配置使用不当造成Proxy CPU负载过高

Posted cicada23

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次redis client配置使用不当造成Proxy CPU负载过高相关的知识,希望对你有一定的参考价值。

背景

在服务的缓存中使用了redis作为分布式缓存,在使用的过程中发现通过对比发现了一个异常现象:即redis proxy 的CPU使用率和请求的QPS不符合。和基础设施inf的同事也沟通过后,也没有一个固定的结论(也可能inf同事没有很认真的关注这个问题)

排查过程

现象发现

一次偶然的过程中,发现单个实例redis客户端连接关闭的QPS特别高,已经达到了8~10K左右的QPS, 这个量已经高于对应实例对于redis请求的QPS了。于是就思考为什么客户端有这么频繁的关闭连接,在没有深入排查的时候,可以有两个猜想:
  1. 客户端连接主动关闭
  2. 客户端连接被动关闭
而目前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,则会被关闭。所以综合分析来看,可能是两点原因导致了这种情况:

  1. redis-v6的连接池使用的是固定连接池的方式,也就是一旦新建,就默认值池子里必须有这么多连接。不是像Java 中redis client 的连接池配置,拥有最大空闲连接,以及最小空闲连接等配置,来保证连接池保持在一个合理的水平。这样导致即使连接池中,有大量空闲连接的情况下,也不会被销毁。
  2. IdleTimeout的参数值的错误使用,导致了大量空闲的链接在不停的销毁重建,从而加重了proxy CPU的负担

修复

将IdleTimeout 配置参数从200ms改到2h之后,测试后发现明显可以降低客户端连接关闭的QPS。 然后进行上线

1. 客户端连接关闭QPS明显降低

2. redis proxy CPU 使用率下降了一倍以上

 

技术图片


技术图片


总结

以上仅个人观点,欢迎批评指正

技术图片

以上是关于记一次redis client配置使用不当造成Proxy CPU负载过高的主要内容,如果未能解决你的问题,请参考以下文章

记一次lua io使用不当导致内存泄露问题

记一次因 Redis 使用不当导致应用卡死 bug 的排查及解决!

记一次因 Redis 使用不当导致应用卡死 bug 的排查及解决!

记一次由Redis分布式锁造成的重大事故,避免以后踩坑!

记一次Chocolatey造成的开发环境失误

记一次nginx配置不当引发的499与failover 机制失效