redis-1:一次关于smember和smembers的使用优化

2019-07-03 18:00:34 浏览数 (1)

在一次业务升级后,发现服务边的不稳定,zabbix各项监控指标相对上线前异常上升。

通过prometheus等监控发现是redis调用大增,看代码在循环里对smember有大量调用。

(1).原因

循环中大量对redis的单次调用会产生大量的redis/io调用,导致业务JVM和redis的cpu的线程上下文和软中断同时飙升。

业务JVM线程上下文和软中断同时飙升原因:

smember的时间复杂度是O(1),很快,jvm大量调用smember后,相关IO也会很快得到返回数据从而进入就绪状态,那么从就绪IO读取数据的线程会始终处于繁忙状态。

而且我们的cpu核数不多,information只有2core,而且jvm访问redis是同步读取,是block的,调用后进入block,cpu必然要切走线程做别的事。

大量/频繁/反复的会进入cpu调度/线程切换的过程中。

大量cpu调度/线程切换–>导致cpu %sy(内核占用cpu时间)飙升。

大量的io事件->导致cpu %si(软中断占用cpu时间)飙升。

关于软中断:

(2).定位过程

循环中大量对redis的单次调用会产生大量的redis/io调用,导致业务JVM和redis的cpu的线程上下文和软中断同时飙升。

top命令:

可以看到%sy(cpu内核时间),%si(cpu软中断时间)非常高,实际上在繁忙时段,%us, %sy, %si可以各占1/3,这个对于%sy, %si非常非常高了,我们希望cpu时间尽可能多的用于%us(cpu 用户态时间)。

zabbix的cpu相关指标:

相关服务的cpu jumps:绿线是上下文切换频率;蓝线是中断频率。

cpu load:相对之前有很大提升。

cpu利用率情况:可以很明显看到cpu的%sy,%si飙升,之所以没有影响线上服务,是因为us%时间够用,仔细观察,%us时间和之前的差不多。

服务线上接口情况:

qps和错误率正常基本没有变化:

接口的延迟时间和往常基本一致:

服务的cpu jumps:

redis实例的cpu load:

redis的cpu 利用率:可以很明显看到cpu的%sy,%si飙升。

观察到这里,只是确定了cpu的使用确实出现了很大的问题,那么接下来我们需要确认cpu为什么高,由何引起。

另外,为什么要先观察grafana和zabbix呢:因为我需要确认一件事:是否影响了线上,决定我是否要回滚,因为回滚代价很高,回滚后不方便定位问题,如果不影响线上,可以短时间内接受。

用两个方法确认这个事情(但可惜最初我没信这个结果,坚持认为redis的smember不会引发,因为速度很快嘛是O(1),但是忽略了IO):

方法一:top -H -p pid

看某个linux下的进程下有哪些线程以及这些线程占用的cpu时间,找到耗费cpu时间最高的线程。

然后将cpu使用率最高的线程id转换为16进制,在jstack pid的结果里去查看这个线程的堆栈:

可以很清晰的看到smember的调用,这次优化上线只有这一处改动,其实这个时候已经可以确认问题了。

方法二:阿里的arthas

arthas安装&使用:https://github.com/alibaba/arthas 这个安装需要高权限,需要运维同学帮忙安装。

使用dashboard命令,有些新东西,有那么7,8个线程始终是RUNNABLE状态,且“永远是”,并且是高耗cpu的线程,一直RUNNABLE说明一直在切换(RUNNING是很难捕获到的)。

再用thread -n 10查找cpu耗损前10的线程堆栈,这个我没有保存现场图,看到的和之前方法一是一样的堆栈,也是smember的堆栈。

原因确认:由于大量调用redis导致io事件爆炸–>从而造成cpu忙不过来。

(3).解决方式

使用smembers一次获取多个数据,JVM内存里运算,规避大量IO事件。

(4).总结

1.循环里不要调用redis

但是像smember这类exist一定是封装在底层的,上层一层层调用很有可能放大,这个只能是在使用的时候注意,同时观察线上的zabbix监控,主要是cpu是否有异动。

我们还在自研框架中集成了prometheus,全方位监控redis的调用。

2.找到一个办法或者参照系去衡量一个服务的cpu健康状态

有的。分两部分来看:

2.1.对于redis的cpu健康状态,我们可以参照redis-passport的cpu指标

只有cpu jumps可以参照,cpu利用率和cpu时间取决于具体怎么用redis,差异太大,看各自的历史曲线。

因为按照正常业务来讲,每次验票都要调用一次redis,这个访问是省不了的,一定量很大,正常服务很难有比这个还高的。如果别的redis的cpu超过了redis-passport的cpu指标,那就需要注意了。下图关键数据脱敏。

2.2.对于nginx来说

由于nginx使用的是epoll,所以软中断个数要远大于线程切换次数,这也是nginx性能很高的一个原因。

2.3.对于一般的应用:

上下文切换次数要高于中断次数。

0 人点赞