(五)redis性能问题
A. redis客户端
- redis客户端通信
- redis新版本对于网络请求进行多线程处理,收到请求后redis实际处理数据依然为单线程模式。
- redis客户端同服务端间的通信基于tcp协议。
- jedis客户端
- 开发阶段将jedis的jar包加入project中,或使用maven添加依赖。
- jedis直连模式
- 是默认方式,但每次使用都会新建tcp连接。
- jedis直连简单方便,但存在对象线程不安全的问题,适用于少量连接的情况。
- jedisPool模式
- 生产多采用该方式,预先生成几个jedis对象于pool中,这些对象保持长连接状态。
- 具体使用时,用getJedisFromPool方法获取对象,再完成后续实际操作。
- 向jedisPool借用jedis对象是本地操作,并无网络开销。控制pool的大小可以有效确定并发的压力,远小于新建tcp的方式。
B. redis内存理解
- 概述:redis所有实时使用的数据都存储于内存中,持久化文件用于备份恢复。本小节九内存消耗、内存管理 & 优化进行阐述分析。
1. 内存消耗
-
内存使用统计
info memory
:查看内存相关指标,重点需要关注的是used_memory_rss, used_memory & 它们的比值mem_fragmentation_ratio。-
阈值注意点
- mem_fragmentation_ratio > 1时,说明redis占用OS的内存并非全是数据存储消耗的,是内存碎片消耗的,两者相差很大则说明碎片率很严重;
- mem_fragmentation_ratio < 1时,说明内存不足时OS进行swap,让硬盘临时替代成内存给redis使用,这导致redis性能急剧下降。
-
核心指标说明
属性名 属性说明 used_memory_human used_memory可读显示 used_memory_rss 操作系统下,redis进程占用的物理内存总量 mem_fragmentation_ratio used_memory_rss / used_memory,大于1就有碎片;小于1则说明有swap给redis用
-
内存消耗分类
- 自身内存:redis空进程自身消耗内存很少,通常used_memory_rss在3M左右,used_memory则约800kb。
- 对象内存:最主要消耗内存的部分,存储用户所有数据。一个对象由key和value两部分组成。
- 缓冲内存:缓冲内存包括客户端缓冲、复制积压缓冲区 & AOF缓冲区。在客户端连接数过大时,可能会造成redis内存飙升。
- 内存碎片:redis默认内存分配器是jemalloc,一般采用固定范围的内存块分配,将内存分为小、大、巨大三个范围。频繁的更新操作(append、setrange等)、大量过期key删除易导致碎片出现。建议重启节点完成碎片整理。
-
子进程内存消耗
- 场景:在AOF或RDB时,fork的子进程理论上会需要和父进程一样的内存空间。基于linux的copy-on-write技术,父子进程可共享物理内存页。但在接受写请求时,父进程仍然需要对修改内容所在的内存页进行复制以完成写操作。
- THP:transparent huge pages机制在linux2.6.38后出现,默认开启,导致复制内存页从4kb变成2mb。若有大量写请求,这会导致内存消耗急剧上升。
2. 内存管理
设置内存上限目的:防止所用内存超出服务器物理内存,一般情况下设置合理的
maxmemory
,保证机器预留20 - 30%闲置内存。动态调整内存上限:
config set maxmemory 10g
&config rewrite
。注意,一旦使用内存上限调整就会激活内存回收。-
内存回收策略
-
过期对象回收:精准维护每个key过期会消耗大量cpu资源,对单线程的redis来说成本过高,一般有两种模式解决。
- 惰性删除:客户端读取到expired的key时,会执行删除操作并返回空值。该方式虽然节约cpu,但存在内存泄漏问题。若expired的key一直未被读取就不会被删除,内存无法释放。
- 定时任务删除:redis内部维护一个定时任务,默认每秒运行10次,通过配置
hz
完成。默认使用慢模式运行,每个数据库空间随机找20个key检查,发现过期时删除对应的key-value。若本次检查中超过25%的key过期,则继续检查20个key,直到低于25%或者运行超时才结束回收的循环。
-
内存溢出控制策略:基于
mexmemory-policy
进行策略的选取 & 确认,可使用config set maxmemory-policy xxx
进行动态配置。策略名 策略具体内容 noeviction 默认策略,不删除任何数据,拒绝所有写入操作并向客户端返回OOM错误信息。 volatile-lru 根据LRU算法删除expired的key,直到空间足够为止。若没有可删除对象,则回退成noeviction策略。 allkeys-lru 根据LRU删除key,不管有无设置超时属性。 allkeys-random 随机删除所有key。 volatile-random 随机删除expired的key。 volatile-ttl 根据key-value的ttl属性,删除最近将要过期的数据,若没有复合的对象回退noeviction策略。
-
4. 内存优化
- 概述:redis所有存储数据都是用redisObject封装,有下方几个字段
字段 | 含义 |
---|---|
type | 当前对象数据类型,主要为string,hash,list,set & zset。注意,key都是string类型。 |
encoding | redis内部编码类型,代表其内部采用的数据结构。 |
lru | 记录对象最后一次被访问的时间。 |
refcount | 记录当前对象被引用的次数,若refcount=0就可以被安全回收。 |
*ptr | 和对象的数据内容相关,若为整数直接存储数据;若非整数则表示指向数据的指针。 |
- 缩减对象:缩减key和value的长度是最直接的方法。
- 共享对象池:redis内部维护0-9999的整数对象池,可通过
object refcount
查看引用次数验证是否启动整数对象池技术。- 效果:使用该技术后,数据内存使用率可降低30%以上。
- 限制:使用maxmemory-policy中的lru策略时(无论allkeys或volatile),redis禁止使用共享对象池。因为共享时,lru字段也需要共享,导致无法获取每个对象最后的访问时间。
- 其他:字符串的优化(预分配机制、字符串重构),encoding类型优化 & 控制key数量。
C. redis缓存设计
- 概述
- 缓存收益
- 加速读写。
- 降低后端负载压力,例如减少数据库访问量和复杂计算。
- 成本代价
- 数据不一致性:缓存层和存储层数据在一定时间窗口内有数据不一致问题,需要存储层更新至缓存层。
- 代码维护成本:新增对于缓存层的代码 & 缓存与存储逻辑的代码。
- 运维成本增加,维护集群状态。
- 缓存收益
1. 缓存更新策略
- LRU、LFU或FIFO算法
- 使用场景:key数量过多,消耗内存达到设置的maxmemory阈值时,会对key-value的剔除。
- 问题:产生一致性问题较严重,删除后导致缓存层数据缺失且运维人员无法及时得知删除信息。
- 配置方法:设置maxmemory大小 & maxmemory-policy算法即可。
- 超时剔除
- 使用场景:缓存数据配置了expire命令,保障key-value在一段时间后失效删除。
- 问题:在一定窗口内有一致性问题。
- 主动更新
- 使用场景:对数据一致性要求较高时使用,若真实数据更新立即更新缓存层。
- 问题:维护成本较高,需开发正完成更新逻辑。
- 案例实践建议
- 低一致性业务:使用最大内存淘汰算法即可。
- 高一致性业务:超时剔除+主动更新,结合使用。
2. redis雪崩
- 现象:redis的大量的key在某个时间段失效或redis服务不可用,导致需要直接访问数据库,大量请求冲击database。
- 解决方案
- 不要设置固定过期时间,尽量在setex时采用随机的ttl。
- 保证缓存层服务高可用性,基于分布式集群架构。
3. redis缓存穿透
- 现象:redis和database都不存在该key对应的数据,但用户不断发起对该类数据的请求导致database挂掉,比如持续大量请求uid的-1的对应数据。
- 解决方案
- 缓存空对象,后续再访问该key可基于缓存层返回null值,存在问题是导致缓存了更多的无用key & 数据不一致问题。该策略适用于数据命中不高,数据频繁变化实时性高的场景。
- 设置database的并发锁,防止大量请求在database上进行。
- 设置拦截器,类似bitmaps或bloom filter,不存在的uid直接拦截在redis层,不可达database。适用于数据命中不高,数据相对固定的场景。
4. redis击穿
- 现象:redis中没有该key但是database中有,一般出现场景为某个hot key过期,大量用户并发查询该key导致直接冲击database。
- 解决方案
- hot key重建
- 定义:缓存失效的瞬间,有大量线程来重建缓存。
- 负面效应:重建时后段附在加大,所以需要减少重建缓存的次数。
- 设置互斥锁
- 互斥锁通过竞争对资源独占使用,执行顺序是乱序的。该方法利用setex或setnx实现,确保只有一个线程重建缓存,其他线程都在等待状态。
- 同步锁更高级,能保证执行顺序。
- 设置hot key永不过期
- 直接不设置过期时间,或者设置一个逻辑过期时间,超过该周期后单独使用一个线程重建缓存。
- 但容易出现数据一致性问题。
- hot key重建
D. pipeline & transaction
- pipeline
- 概述:管道技术是客户端行为,对redis server透明,server不知道发过来的请求是普通的还是pipeline形式的。
- 使用效果:类似批量的方式缺失降低了网络开销,利用多个命令一次网络发送提升了效率。基于hgetall的操作并没有对应的mhgetall可用,此时pipeline可派上用场。
- transaction
- 概述:事务是redis server行为,当客户端使用了MULTI命令后把客户端对象设置为特殊状态。客户端发出的命令会被缓存到server,等到EXEC再执行。
- 使用效果:客户端在exec前的命令,都会被server返回一个queued,事务模式能提高网络使用效率,但不支持回滚。
E. 异常排查 & 配置优化
1. Jedis客户端常见异常
-
无法从连接池获取到连接:jedisPool的jedis个数有限(默认8个),若他们都被使用时,那么第9个来调用的就会报错。
- 报错信息:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get get a resource from the pool
- 高并发场景下,连接池的jedis个数可进行调整。若我们认为一个jedis每条命令处理时间5ms,1s单个jedis对象极限能处理200个命令,8个jedis的qps极限约为200*8=1600。若qps预计在1.6w的话,需将jedis数量调整至80。
- 若存在慢查询,那么这个jedis也会被长时间占用不归还,可能造成该异常。
- 报错信息:
-
读写超时
- 报错信息:
redis.clients.jedis.exceptions.JedisConnectionException & java.net.SocketTimeoutException: read timed out
- 读写超时设置的太短。
- 读写操作耗时太久,需优化具体逻辑。
- 报错信息:
-
连接超时
- 报错信息:
redis.clients.jedis.exceptions.JedisConnectionException & java.net.SocketTimeoutException: connect timed out
- 连接超时设置太短,修改参数
jedis.getClients().setConnectionTimeout(A larger value)
。 - redis发生阻塞,tcp-backlog满了导致新连接失败。
- 服务端与客户端间的网络存在问题。
- 报错信息:
-
客户端缓冲区异常
- 报错信息:
redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream
- jedis输出的缓冲区大小偏小,若set bigkey,可能就会报错.
- jedis不正常并发读写,被多个线程操作。
- 报错信息:
-
其他场景异常
lua脚本正在执行:正在运行lua脚本时会报错。
加载持久化文件:正在加载rdb文件时会报错。
内存使用情况超出maxmemory:若redis使用内存过多,超出预设的maxmemory会报错OOM,需要扩容调整。
-
客户端连接数过大:超出预设的maxclients时,会报错,具体解决手段有两个大方向
客户端:若maxclients设置的阈值不小,则一般是客户端使用不当导致的,可下线部分应用节点先降低总连接数。
服务端:基于服务端一般是高可用模式,可采用故障转移机制,下线有问题的redis节点,完成主从切换后再排查。
-
实际案例
- redis内存陡增:master内存狂增几乎打满maxmemory,但slave内存几乎无变化,导致无法写入新数据,报出OOM的错误。
- 确有大量key写入:基于slave内存无变化的现象,可初步判定主从间复制出现问题,比较两侧dbsize进行复核。
- 其他原因:造成master压力大可能是jedis等客户端缓冲区导致的,比如客户端使用monitor命令,可使用info clients确认。
- 客户端周期性超时:redis服务端无明显异常,可发现部分慢查询。
- 网络原因导致该现象出现。
- redis服务端自身问题,需查log文件再确认。
- 查询慢查询时间点,查看客户端日志中是否存在
redis.clients.jedis.exceptions.JedisConnectionException
&java.net.SocketTimeoutException: connect timed out
的错误信息。
- redis内存陡增:master内存狂增几乎打满maxmemory,但slave内存几乎无变化,导致无法写入新数据,报出OOM的错误。
2. 运维配置优化
a. Linux - 内存分配控制overcommit
定义:Linux对于大部分申请内存的请求回复为yes,便于运行更多的程序。大多数情况下,申请完内存后并不会马上使用内存,该技术即为overcommit。本处内存代表,物理内存+swap之和。
-
参数具体可选值
值 含义 0 表示内核将检查是否有足够的可用内存,若有足够内存,则申请通过;若没有足够的可用内存,则申请失败。 1 表示内核允许超量使用内存直到耗尽。 2 表示内核绝不过量使用内存(never overcommit),系统整个内存地址空间不超过swap+50%RAM值。 常见场景:redis启动时,日志中出现
WARNING overcommit is set to 0!...
-
redis中的解决方案:上述日志提示修改
vm.overcommit_memory=1
,便于持久化操作的fork能在低内存下执行成功。- 获取参数:
cat /proc/sys/vm/overcommit_memory
- 设置参数:
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
,并进行sysctl vm.overcommit_memory=1
- 获取参数:
b. Linux - 内存交换swappiness
定义:物理内存不足时将一部分内存页进行swap操作——将硬盘暂时作内存使用。若遇到高并发、高吞吐的应用,此时磁盘IO会成为系统瓶颈。在Linux 下,并不是物理内存使用完才会使用swap,具体何时使用swap基于swappiness参数。
-
参数含义:swappiness取值范围在 0 -100间,默认值为60,该值越大意味着操作系统使用swap的概率越高。
值 策略 0 Linux3.5及更新版本,宁愿OOM killer也不用swap。 1 Linux3.5及更新版本,宁愿用swap也不用OOM killer。 60 默认值。 100 Linux会主动使用swap。 -
设置方法
- 配置swappiness:使用
echo {bestvalue} > /proc/sys/vm/swapiness
- 确保重启依然生效:使用
echo vm.swappiness={bestvalue} >> /etc/sysctl.conf
- 配置swappiness:使用
-
常见场景
- redis在物理内存充足时运行极快,物理内存不足时该配置可避免redis死掉。
- 若redis时高可用的情况下,宁愿死掉也不要使用swap,因为这会导致阻塞。
- 除了直接free -h查看swap情况,亦可参考mem_fragmentation_ratio,若 < 1则存在redis使用swap的情况。
c. Linux - THP
- 定义:Linux kernel在2.6.38后增加了THP特性,默认开启。全称Transparent Huge Pages,支持大内存页2MB分配,可加快fork子进程的速度,但fork操作后每个内存页从4KB增加到2MB,会大幅度增加重写期间父进程的内存消耗,每次写命令引起的复制内存页单位放大了512倍。
- 参数设置
- 禁用THP特性:使用命令
echo never > /sys/kernel/mm/transparent_hugepage/enabled
- 确保重启依然生效:编辑开机配置文件/etc/rc.local,追加禁用THP特性命令——
echo never > /sys/kernel/mm/transparent_hugepage/enabled
- 禁用THP特性:使用命令
- 常见场景
- redis启动时日志中出现
WARNING you have Transparent Huge Pages (THP) support enabled in your kernel...
,需手动禁用THP。 - 部分版本的Linux 未把THP放入/sys/kernel/mm/transparent_hugepage/enabled,例如Red Hat 6以上的版本即将THP配置放入/sys/kernel/mm/redhat_transparent_hugepage/enabled,但redis将检查THP的位置写死导致不能成功检测THP问题并将之暴露于日志中,但THP的问题依旧存在需要手动调整。
- redis启动时日志中出现
d. Linux - OOM Killer
- 定义:在内存不足时选择性地杀掉用户进程,OOM Killer会为每个用户进程设置一个权值,该值越高越容易被优先干掉。
- 参数设置
- 参数位置:每个进程的权值位于/proc/{pid}/oom_score中,受到/proc/{pid}/oom_adj的控制。
- oom_adj
- 当oom_adj设置为最小值时,该进程不会被干掉。不同Linux 版本的最小值不同,可参考Linux 源码中的oom.h(-15到-17)。
- 设置方法为
echo {value} > /proc/{pid}/oom_adj
- 适用场景:为保证redis在主机内存使用率接近满载时仍然能存活下来,可将redis进程对应的oom_adj设置成最小值。
e. Linux - 其他参数
-
NTP
- 定义:网络时间协议,全称network time protocol,保证不同机器的时钟一致性。
- 场景:redis集群有多节点组成,涉及多主机问题。时钟不一致会导致redis集群内问题排查难度提升,例如无法有效判别clutser故障转移。
- 解决手段:建议每小时使用一次时钟同步,保障其集群内时钟一致性。具体为
0 * * * * /usr/sbin/ntpdate ntp.xx.com > /dev/null 2>&1
-
ulimit
-
定义:查看和设置系统当前用户进程的资源数,常用命令
ulimit -a
,其展示效果可见下方。category unit value core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 61370 open files (-n) 4096 POSIX message queues (bytes, -q) 819200 max user processes (-u) 61370 -
场景
redis允许同时多个客户端通过网络进行连接,可通过服务端配置
maxclients
参数来进行具体客户端数的限制。redis除了供给客户端连接数所需的10000FD句柄,还需要32个FD供redis自身内部使用。
-
Linux系统下,所有网络连接都是文件句柄,若当前open files为4096,则启动redis时会有下方日志
You requested maxclients of 10000 requiring at least 10032 max file descriptors...Current maximum open files is 4096...
解决手段:将open files的值设置为65535,通过命令
ulimit -Sn 65535
实现。
-
-
TCP backlog
- 定义
- backlog是一个连接队列,在Linux内核2.2之前,backlog包括半连接状态和全连接状态两种队列大小;在Linux内核2.2之后,分离为两个backlog来分别限制半连接(SYN_RCVD状态)队列大小和全连接(ESTABLISHED状态)队列大小。
- 半连接状态:服务器处于Listen状态时收到客户端SYN报文时放入半连接队列中,即SYN queue(服务器端口状态为:SYN_RCVD)。
- 全连接状态:TCP的连接状态从服务器(SYN+ACK)响应客户端后,到客户端的ACK报文到达服务器之前,则一直保留在半连接状态中;当服务器接收到客户端的ACK报文后,该条目将从半连接队列搬到全连接队列尾部,即 accept queue (服务器端口状态为:ESTABLISHED)。
- 场景:redis默认tcp-backlog值为511,若Linux系统的值小于511则会在启动日志中出现
WARNING: THE TCP backlog setting of 511 cannot be ...
- 解决手段
- 查看系统值:通过命令
cat /proc/sys/net/core/somaxconn
查看accept queue具体值,redis不涉及修改syn queue的问题,若想查看可通过cat /proc/sys/net/ipv4/tcp_max_syn_backlog
进行查询。 - 修改:使用
echo 511 > /proc/sys/net/core/somaxconn
进行变更。
- 查看系统值:通过命令
- 定义
f. 数据恢复手段
- AOF机制恢复
- 场景:若误操作flushall或flushdb导致数据丢失,需借助持久化文件进行恢复。若开启了
appendonly yes
,则误操作仅在AOF文件中追加了一条操作记录。 - 解决手段
- 若AOF文件重写了,则之前的数据就无法顺利找回,所以需要调整
auto-aof-rewrite-percentage
&auto-aof-rewrite-min-size
阻止AOF自动重写,并拒绝手动bgrewriteaof。 - 确保上方参数设置后,将AOF文件中的flush操作去掉,并确保AOF文件格式正常以保障数据的顺利恢复。
- 若AOF文件重写了,则之前的数据就无法顺利找回,所以需要调整
- 场景:若误操作flushall或flushdb导致数据丢失,需借助持久化文件进行恢复。若开启了
- RDB机制恢复
- 前置配置:若rdb持久化设置中有自动策略,例如
save 900 1
这类,则因为flush一般涉及key value都较多,会导致RDB恢复基本无望。除非并无开启rdb自动策略,否则面对flush误操作,rdb不能有效恢复数据。 - 解决手段:若redis没有进行rdb自动策略,仅有手动的bgsave所得的rdb文件,那么即可使用rdb文件恢复数据,但相对数据完整性不如aof机制。
- 前置配置:若rdb持久化设置中有自动策略,例如
F. redis阻塞问题
- 概述
- redis属于典型的单线程架构,读写操作皆于唯一的主线程中完成。
- redis处于高并发场景时,主线程若出现阻塞则问题严重,阻塞时jedis会抛出JedisConnectionException异常。
- 阻塞内因:不合理使用api或数据结构,cpu饱和、持久化阻塞(basave的fork瞬间或save持久化)。
- 阻塞外因:cpu竞争、内存交换(swap)、网络问题。
1. 内因详解
a. api或数据结构使用不合理
场景案例:redis正常场景下执行命令速度极快(微妙级),若执行
hgetall
命令且该hash类型的key是big key,则执行速度就会很慢,属于是典型的不合理使用api & 数据结构。-
慢查询
- 定义:默认配置
slowlog-log-slower-than 10000
,即10ms以上的操作被判定为慢查询,slowlog-max-len 128
默认存储慢查询日志长度为128条。 - 获取结果:
slowlog get 10
,即获得最近的10条慢查询命令。
- 定义:默认配置
-
解决方案
定制慢查询阈值:线上一般可设置1ms为慢查询阈值,即
slowlog-log-slower-than 1000
,该速度下单机redis的qps将被限制为1000左右。拓展慢查询日志长度:增加日志长度帮助排查故障的原因,将
slowlog-log-slower-than
可设置为1000。其他:使用快速命令
hgetall
改成hmget
,并禁用keys、sort命令,并将big key拆分。
-
大键big key问题
- 定义
- big key指value所占空间很大的key,一般地,string类型的key,若其value大于10KB即为big key。其他类型的big key,是因为包含的元素过多。
- 一个string类型的key,value最大为512MB;一个list类型的key,其value最多可存储(2的32次方-1)个元素。
- 危害
- 内存空间不均:若在redis集群模式下,bigkey必须被分配至某个节点,导致内存空间使用不均匀。
- 阻塞:因为操作big key容易耗时过高,导致阻塞时间偏长。
- 网络拥塞:每次获取big key产生的网络流量较大,占用带宽。
- 检测big key
- 被动收集:big key在被访问时,出现异常日志。通过这种情况探测出的big key,即为被动收集的模式。
- 主动检测:使用
redis-cli -h xxx.xxx.xxx.xxx -p 6379 --bigkeys
探测,其本质是使用scan渐进式遍历,rdr也是很好的redis分析工具,在从节点上操作时更合适的手段。
- 删除big key
- string类型:该类的big key一般可以直接
del key
即可,不会阻塞。 - hash、list、set & sorted set:直接删除依然会导致阻塞,若为hash类型的场景下,可结合hscan逐步获取进行删除。
- Redis4.0后特性:使用lazy delete free模式,删除big key不会阻塞。
- string类型:该类的big key一般可以直接
- 定义
-
热键hot key问题
- 常见场景:热点突发新闻、热销大卖商品会给系统带来巨大流量,在集群模式下存储这些信息的redis节点会出现流量不均匀的情况,存储热点消息的节点出现QPS压力偏大的问题。
- 判别方法
- 代理端:若客户端对redis服务端的请求,是基于Twemproxy/Codis这样的分布式架构,则所有的请求都通过代理端再达到服务端,可在代理处完成数据统计分析big key。
- 服务端:使用monitor在server处分析客户端大量请求中的hot key,但是使用monitor会影响redis性能,且该方法是针对redis单节点进行的,若为集群模式还需要聚合统计。
- 解决方案
- 拆分复杂数据结构:若为二级结构(list、hash等),等可拆分成若干key-value,分散至redis集群的各个节点上。
- 迁移:将hot key所在的slot单独放置到一个redis节点。
- 本地缓存:将hot key放在业务端的本地缓存中,处理速度更快且降低redis压力,但需要注意数据一致性问题。
b. cpu饱和
- 简述:redis处理命令时为单线程,仅有主线程处理业务,所以可用cpu数为1。若单核cpu的主机,则使用率接近100%,可使用top命令或
redis-cli -h xxx.xxx.xxx.xxx -p 6379 --stat
查看使用情况,stat中connections是历史总连接数累计,实时数据可看lsof -i:6379 | wc -l
。 - 解决方案
- 若看到qps已经足够高(一般可达6w),那么此时垂直优化较困难,建议通过集群化完成水平扩展。
- 若发现qps仅数千甚至更低,那么大概率使用了高时间复杂度的命令,需修改业务方法。
- 内存优化过度,可用
info commandstats
检查,比如hset耗时过长,为节约内存空间放宽ziplist的使用条件,可修改hash-max-ziplist-entries
&hash-max-ziplist-values
。
c. 持久化导致堵塞
前置说明:不考虑使用
save
的场景,这种模式会导致长时间redis堵塞。fork阻塞:rdb和aof重写恢复数据时,主线程会fork生成子进程,子进程再完成实际的重写。若fork耗时过长,则可能导致阻塞出现;
aof阻塞:开启aof后,文件刷盘的方式一般为1s一次,后台线程对aof文件进行fsync操作,当磁盘压力大时fsync会需要等待,其主要缘由是磁盘压力偏大。
2. 外因详解
- cpu竞争
- 进程竞争:redis属于cpu密集型应用,虽然一般情况下cpu不是性能瓶颈,但是不建议和其他服务部署在一台主机上。
- 绑定cpu:redis为尽可能地充分利用多核cpu,通常会一台机器部署多个redis实例。此场景下,不同redis进程绑定对应的cpu可降低上下文切换的开销,但是某个实例进行rdb或者aof持久化时,可能因为绑定cpu会导致压力过大。
- 内存交换swap
- 简述:swap空间即内存空间不足时,将磁盘充当内存使用。
- 甄别手段
- 找到redis pid,通过
redis-cli -p 6379 info server | grep process_id
; - 接着根据id查询swap信息,使用
cat /proc/xxxx/smaps | grep swap
。正常情况下应该都是0kb或者少量4kb。
- 找到redis pid,通过
- 网络问题
- 拒绝连接:网络割接或带宽耗尽时的网络闪断(
sar -n DEV
查看),redis超过默认1w的maxclients连接数,或者是进程限制打开文件数(ulimit -n)、backlog队列溢出。 - 网络延迟:客户端到redis服务器之间网络延迟,可使用
redis-cli -h xxx.xxx.xxx.xxx -p 6379 --latency
核验。 - 网卡软中断:高并发下单个网卡队列只能使用一个cpu,新版redis已经改进网络方面效率问题。
- 拒绝连接:网络割接或带宽耗尽时的网络闪断(