背景

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

排查过程

现象发现

一次偶然的过程中,发现单个实例redis客户端连接关闭的QPS特别高,已经达到了8~10K左右的QPS, 这个量已经高于对应实例对于redis请求的QPS了。于是就思考为什么客户端有这么频繁的关闭连接,在没有深入排查的时候,可以有两个猜想:

  1. 客户端连接主动关闭
  2. 客户端连接被动关闭

而目前redis客户端连接均由连接池管理,也就是连接池中的连接在不停的建立,关闭,建立,关闭~

redis qps限制 redis qps过高_客户端

 

排查过程

所以,问题就到了,为什么连接池中的连接不停的在管理和建立呢?在此,需要看下使用对应语言的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 qps限制 redis qps过高_redis_02

redis qps限制 redis qps过高_客户端_03

总结

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