//
Redis开发与运维学习笔记---(12)
//
阻塞
Redis是典型的单线程架构,所有的读写都是在一条主线程中完成的,在高并发场景中,一旦这条主线程出现了阻塞,哪怕是很短的时间,对于应用的影响都是巨大的。
Redis的阻塞,一般来讲,是由以下原因构成的:
不合理的使用API或者数据结构、CPU饱和、持久化阻塞等内在原因;
CPU竞争、内存交换、网络问题等外在原因
下面我们分别分析这些原因。
01
API或者数据结构不合理
通常情况下,Redis是内存中进行操作的,理论上执行速度是非常快的,但是如果对象的数据很多,例如包含1w个元素的hash结构执行hgetall操作,该算法的复杂度是O(n),因此执行命令就会变的很慢。
如何发现慢查询?
Redis原生的慢查询统计功能可以很好的统计慢查询情况,使用slowlog get {n}命令可以获取最近的n条慢查询指令,默认对于执行超过10ms的命令,都会记录在一定长队列中,线上实例建议设计为1ms,便于发现毫秒级以上的命令。其实关于慢查询,之前的文章中讲过一点,给出连接:
如何调整慢查询?
慢查询的调整可以按照下面两个方向进行:
1、修改为低算法度的命令,例如hgetall改为hmget等、禁用keys、sort命令
2、调整大对象:缩减大对象数据或者把大对象拆分为多个小对象,防止一次命令中操作过多的数据。
如何发现大对象?
redis本身提供发现大对象(大key)的工具,对应命令:
redis-cli -h {ip} -p {port} bigkeys
[root@VM_48_10_centos ~]# redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Biggest string found so far 'backup3' with 101 bytes
-------- summary -------
Sampled 4 keys in the keyspace!
Total key length in bytes is 28 (avg len 7.00)
Biggest string found 'backup3' has 101 bytes
4 strings with 398 bytes (100.00% of keys, avg size 99.50)
0 lists with 0 items (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 hashs with 0 fields (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)
02
CPU饱和
单线程的redis处理命令时只能使用一个CPU,而CPU饱和是指redis将单核CPU使用率跑到100%,而不是整个机器的使用率达到100%,使用top命令很容易能够识别出对应redis进程的CPU使用率,CPU饱和是非常危险的,他使得redis无法处理更多命令,严重影响吞吐量和应用的稳定性。
在redis中,可以使用--stat来查看当前redis的使用情况:
[root@VM_48_10_centos ~]# redis-cli --stat
------- data ------ --------------------- load -------------------- - child -
keys mem clients blocked requests connections
4 1.91M 5 0 425068 (+0) 737
4 1.91M 5 0 425070 (+2) 737
4 1.91M 5 0 425072 (+2) 737
4 1.91M 5 0 425074 (+2) 737
4 1.91M 5 0 425076 (+2) 737
4 1.91M 5 0 425078 (+2) 737
一个常见的案例是滥用ziplist编码(修改hash_max_ziplist_entries值和hash-max-ziplist-value配置),该编码的计算复杂度在O(n)到O(n²)之间,例如一个hash对象中存储了大量的元素,采用该编码方式虽然会降低内存的使用率,但是操作变得更慢,而且更消耗CPU,ziplist压缩编码是Redis用来平衡空间和效率的优化手段,不可过度使用。
03
持久化阻塞
持久化引起的阻塞情况主要有:
fork阻塞、AOF刷盘阻塞、HugePage阻塞等
fork阻塞:
fork操作发生在RDB和AOF重写时,Redis主线程调用fork操作产生共享内存的子进程,有子进程完成持久化文件重写工作,由于fork操作本身耗时较长,必然会导致主线程的阻塞;可以执行info stats命令获取到latest_fork_usec指标,表示redis最近一次fork操作耗时,如果耗时很大,则需要作出优化调整。
AOF刷盘阻塞:
当我们开启AOF持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对AOF文件做fsync操作,当硬盘压力过大时,fsync操作需要等待。如果主线程发现举例上次的fsync成功超过2秒,为了数据安全性,它会阻塞知道后台线程执行fsync操作完成,这种阻塞是由于磁盘压力引起的。
当然,也可以查看info persistence统计中的aof_delayed_fsync指标,每次发生fdatasync阻塞主线程时该指标会累加。
HugePage写操作阻塞
子进程在执行重写期间利用Linux写时复制技术降低内存开销,因此只有写操作时Redis才复制需要修改的内存页,对于开启了Transaparent HugePages的操作系统,每次写命令的复制内存页从4k变为2Mb,会拖慢写操作的执行时间,导致大量写操作慢查询。
04
CPU竞争
CPU竞争问题主要分为下面两类:
进程竞争:Redis是典型的CPU密集型应用,不建议和其他多核CPU密集型服务部署在一起,其他进程过度消耗CPU时,会严重影响Redis吞吐量,可以通过top,sar等命令定位到CPU消耗的时间点和具体进程。
绑定CPU:部署Redis时一般都是单机多实例,这样能够最大程度的利用好多核CPU的优势,我们可以将Redis绑定到CPU上,这样,可以降低CPU频繁进行上下文切换的开销。但是这种方法有一个缺点,在进行RDB或者AOF文件重写时,如果做了绑定CPU的操作,则父进程与子进程将产生激烈的CPU竞争,极大影响Redis稳定性。因此,对于主节点一般不建议进行CPU绑定。
05
内存交换
swap对于Redis来说是非常致命的,Redis的高性能是建立在内存上的,如果系统把Redis的部分内存交换到硬盘,由于内存与硬盘读写速度差好几个数量级,会导致发生交换之后的redis性能急剧下降。
如何识别是否发生了内存交换?
1、使用redis-cli info server|grep process_id找到redis的进程id
2、使用cat /proc/process_id/smaps|grep Swap来查看内存交换信息
[root@VM_48_10_centos ~]# redis-cli info server|grep process_id
process_id:10539
[root@VM_48_10_centos ~]# cat /proc/10539/smaps |grep Swap
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
Swap: 0 kB
如果交换量都是0kb或者个别的4kb,则是正常现象说明redis进程内存没有被交换。
为了防止内存被交换,可以降低Linux系统使用swap优先级例如修改/proc/sys/vm/swappiness。
06
网络问题
网络问题经常会引起Redis的阻塞。常见的网络问题有:网络闪断、redis连接拒绝、连接溢出、网络延迟、网卡软中断
分别来介绍,先来看网络闪断:
1、网络闪断
一般发生在带宽耗尽或者网络切换瞬间,通常可以使用sar -n DEV来查看本机的历史流量是否正常。这里强调一点,尽量避免跨机房的Redis调用
2、redis连接拒绝,redis通过maxclients参数控制客户端的最大连接数,默认1000,当连接数大于这个阈值,则会拒绝连接进入,info stats的rejected_connections统计指标记录有被拒绝连接的数量。
[root@VM_48_10_centos ~]# redis-cli info stats|grep rejected_connections
rejected_connections:0
3、连接溢出
连接溢出分为进程限制和backlog队列溢出两种常见情况。
进程限制是指Redis对于进程使用的资源进行限制,其中一项是对进程可打开的最大文件数控制,通过ulimit -n查看,默认是1024
backlog队列溢出
系统对于特定端口的TCP连接使用backlog队列保存,Redis的默认长度是511,通过tcp-backlog参数设置,如果Redis用于高并发场景为了防止缓慢连接占用,可适当增大这个值,让它大于系统允许值(默认是128)。可以使用netstat -s查看因backlog队列溢出造成的连接拒绝统计。
4、网络延迟
常见的网络物理拓扑按照网络延迟由快到慢可分为:同物理机>同机架>跨机架>同机房>同城机房>异地机房,而容灾性恰好相反,同物理机容灾性最低而异地机房容灾性最高。redis提供了测量机器之间网络延迟的工具。分别是--latency,--latency-history,--latency-dist,这三个参数的具体内容:
--latency选项可以测试客户端到目标redis的网络延迟,但是只输出一条信息
--latency-history测试网络延迟,可以分段测试延迟,每15s输出一次
--latency-dist会使用统计表的形式从控制台输出延迟统计信息
网络带宽的占用主要是根据当时使用率是否达到瓶颈有关,如果频繁操作redis的大对象,对于千M网卡都很容易达到网卡瓶颈。因此需要重点监控机器流量,及时发现网络延迟等情况。
5、网卡软中断
网卡软中断是指由于单个网卡队列只能使用一个CPU,高并发下网卡数据交互都集中在一个CPU中,导致无法充分利用多核CPU的情况,网卡软中断一般出现在网络高流量吞吐的场景。软中断指标一般是top命令中CPU性能的si这一项。本例中,si为0
23:25:25 up 38 days, 6:46, 1 user, load average: 4.44, 4.75, 4.95
Tasks: 83 total, 1 running, 81 sleeping, 0 stopped, 1 zombie
%Cpu0 : 93.0 us, 5.3 sy, 1.7 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st