Redis
服务器连接不上,应用程序无法获取连接。可能是以下的原因,以及对应解决的方案
原因
Redis
服务器默认设置的最大连接数 maxclients
是 10000,但是这个受服务器最大文件数影响,服务器默认最大文件数是 1024,所以 redis
最大连接数为 1024-32=992
解决方案
./redis-cli –h host –p port info clients
./redis-cli –h host –p port config get maxclients
# 修改 /etc/security/limits.conf,添加下面内容
* soft nofile 65536
* hard nofile 65536
# 重启服务生效后,使用 ulimit -a 查看
原因
监控平台查看到 redis
客户端连接数过大,超过最大限制 1W
通过下面命令分析具体的连接信息
./redis-cli –h host –p port client list
如果存在大量的空闲连接,主节点 cmd=null
,从节点 cmd=readonly
,并且 idle
空闲时长太长,导致连接池维持了太多的连接。
默认情况下,如果客户端空闲了很多秒,Redis 的最新版本不会关闭与客户端的连接:连接将永远保持打开状态。
解决方案
timeout
./redis-cli –h host –p port CONFIG SET timeout 30
tcp-keepalive
# 用来定时向client发送tcp_ack包来探测client是否存活的
./redis-cli –h host –p port config set tcp-keepalive 300
在 Redis
官方文档 reids面试题 有这么一句话:CPU 基本不可能成为的 Redis 的瓶颈,通常 Redis 受限于内存或网络
然而在实际生产环境中,还是会遇到 redis cpu
占用过高的场景
原因
高消耗命令 (慢查询 或 value 值过大) :即时间复杂度为 O(N) 或更高的命令。通常情况下,命令的时间复杂度越高,在执行时会消耗较多的资源,从而导致 CPU 使用率上升
这是因为 Redis
是用 “单线程-多路复用io模型” 来实现高性能的内存数据服务的,这种机制避免了使用锁,但是同时这种机制在进行 sunion 之类的比较耗时的命令时会使 redis 的并发下降。因为单线程,所以耗时的命令会导致并发下降,读写并发都会下降,从而导致 CPU 使用率上升
解决方案
redis
架构为读写分离架构,对高消耗命令或应用进行分流参考前面的 Redis 连接数问题
原因
Redis
提供两种持久化方式:RDB (默认) 和 AOF (阿里云默认)
RDB 是 Redis 默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为 dump.rdb。通过配置文件中的 save 参数来定义快照的周期
AOF 持久化(即 Append Only File 持久化),则是将 Redis 执行的每次写命令记录到单独的日志文件中,当重启 Redis 会重新将持久化的日志中文件恢复数据
在 Redis 4.0
之后有混合持久化的功能,bgsave
的全量和 aof
的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。如果在 RDB 和 AOF 两个都配了优先加载 AOF
如果开启了 AOF,当服务器处于高负载状态时,频繁执行 AOF 会一定程度导致 CPU 使用率升高
解决方案
Redis 内存不足时,要么 Redis 被 Linux 内核 OOM 杀掉,抛出错误崩溃,要么就会卡顿。随着现代操作系统 malloc 方法通常都不返回 NULL,而是服务器开始交换,因此 Redis 性能降低,会出现错误;而 Redis 内置保护措施允许用户在配置文件中使用 maxmemory
选项,设置 Redis 最大占用内存。而达到最大限制,Redis 就会返回错误给写命令 (会继续接受读命令)
下面分析一下内存占用过高的场景
key 较多,需要跟研发确认 key 是否有合理设置过期时间、若是数据都是永久数据不下线的话,就要考虑增加内存,这种是属于正常情况
查看内存碎片
./redis-cli –h host –p port INFO memory
主要看 mem_fragmentation_ratio
字段
>1&&<1.5
:合理的范围,说明操作系统分配的内存总是总是大于实际申请的空间,碎片不多>1.5
:内存碎片率已经超过50%,需要采取一些措施来降低碎片率<1
:实际分配的内存小于申请的内存了,很显然内存不足了,这样会导致部分数据写入到 Swap 中swap 对于操作系统来比较重要, 当物理内存不足时, 可以将一部分内存页进行 swap 操作, 以解燃眉之急
swap 空间由硬盘提供, 对于需要高并发、 高吞吐的应用来说, 磁盘 IO 通常会成为系统瓶颈。当然内存达到了 Redis 的规则,会触发内存淘汰机制
之后 Redis 访问 Swap 中的数据时,延迟会变大,性能会降低
内存碎片清理
# 查看
./redis-cli –h host –p port config get activedefrag
# 开启自动内存碎片整理(总开关) activedefrag yes
./redis-cli –h host –p port config set activedefrag yes
127.0.0.1:6379> memory purge
自动清理触发机制
影响
原因
解决方案
对大 Key 进行拆分
对于字符串类型的 key,我们通常要在业务层面将 value 的大小控制在 10KB左右,如果 value 确实很大,可以考虑采用序列化算法和压缩算法来处理,推荐常用的几种序列化算法: Protostuff、Kryo 或者 Fst
对于集合类型的 key,我们通常要通过控制集合内元素数量来避免 bigKey,通常的做法是将一个大的集合类型的 key 拆分成若干小集合类型的 key 来达到目的
对大 Key 进行清理:将不适用 Redis 能力的数据存至其它存储,并在 Redis 中删除此类数据
对过期数据进行定期清理:堆积大量过期数据会造成大 Key 的产生,例如在 HASH 数据类型中以增量的形式不断写入大量数据而忽略了数据的时效性。可以通过定时任务的方式对失效数据进行清理
影响
解决方案
原因
高并发访问热 key,请求的数据在 DB 中存在,但是 Redis 缓存过期,后端会直接从 DB 中加载数据给前端,并且写入 Redis;高并发访问会导致 DB 压力急剧增大,导致服务不可用
解决方案
过期时间 + 随机值
对于热 key,不设置过期时间;但就会引起 Redis 内存占满问题
过期时间 + 随机值;对于相同业务写缓存时,在基础的过期时间上,加一个随机过期时间,让数据错峰过期,避免瞬间全部过期,对 DB 造成巨大压力
预热:提前把 热门数据提前写入 Redis 中,并设置过期时间最大值;可以用于一些已知一定会出现高并发的场景,如双十一、双十二、热点新闻、游戏活动等等
使用锁:但缓存失效时,先获取分布式锁,获取锁成功才执行 DB 事务,否则等待一段时间再次获取锁
// 伪代码
public Object getData(String id) {
String desc = redis.get(id);
// 缓存为空,过期了
if (desc == null) {
// 互斥锁,只有一个请求可以成功
if (redis(lockName)) {
try
// 从数据库取出数据
desc = getFromDB(id);
// 写到 Redis
redis.set(id, desc, 60 * 60 * 24);
} catch (Exception ex) {
LogHelper.error(ex);
} finally {
// 确保最后删除,释放锁
redis.del(lockName);
return desc;
}
} else {
// 否则睡眠200ms,接着获取锁
Thread.sleep(200);
return getData(id);
}
}
}
原因
缓存穿透:查询一个不存在的数据,即 Redis 和 DB 都不存在
导致每次请求都会 穿透 到 DB,缓存成了摆设,同样会对数据库造成很大压力影响性能
解决方案
布隆过滤器的原理参考:https://developer.aliyun.com/article/773205
原因
缓存雪崩是指大量请求无法被 Redis 处理,请求直接打到 DB,导致数据库压力剧增或宕机
两种原因:
缓存雪崩是发生在大量数据同时失效的场景,而缓存击穿 (失效) 是在某个热 key 失效的场景
解决方案
限流指在业务系统的请求入口前端控制每秒进入系统的请求数,避免过多的请求被发送到数据库
解决方案
[1] Redis client handling
[2] Redis 性能排查与调优
[3] 为什么 CPU 结构也会影响 Redis的性能?
[4] The Top 6 Free Redis Memory Analysis Tools
[5] How to analyze and optimize memory usage in Redis
[6] Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?