4Redis常见问题
redis热点key的一些问题
热点检测
- 凭借经验,进行预估:例如提前知道了某个活动的开启,那么就将此Key作为热点Key
- 客户端收集:在操作Redis之前对数据进行统计,记录每个请求,定时把收集到的数据上报,然后由一个统一的服务进行聚合计算。方案简单但是无法适应多语言架构
- 抓包进行评估:Redis使用TCP协议与客户端进行通信,通信协议采用的是RESP,所以能进行拦截包进行解析
- 在proxy层,对每一个 redis 请求进行收集上报,
- Redis自带命令查询:Redis4.0.4版本提供了
redis-cli –hotkeys
就能找出热点Key- 如果要用Redis自带命令查询时,要注意需要先把内存逐出策略设置为allkeys-lfu或者volatile-lfu,否则会返回错误。进入Redis中使用
config set maxmemory-policy allkeys-lfu
即可。
- 如果要用Redis自带命令查询时,要注意需要先把内存逐出策略设置为allkeys-lfu或者volatile-lfu,否则会返回错误。进入Redis中使用
解决方案
服务端缓存
- 即将热点数据缓存至服务端的内存中
保证Redis和服务端热点Key的数据一致性
备份热点Key
- 即将热点Key+随机数,随机分配至Redis其他节点中。这样访问热点key的时候就不会全部命中到一台机器上了。
- 一种案例:将热点Key拆分后,需要轮询所有的key以保证库存卖光,但是从1开始按顺序轮询会导致性能低,使用随机轮询,并且将卖光的库存分片key,进行记录
Redis为什么这么快
- 基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。它的,数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
- 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用多路I/O复用模型,非阻塞IO
- 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;swap的基本单位是page,Redis的消息达不到那么大,
缓存穿透、缓存击穿、缓存失效
缓存穿透
- 什么叫缓存穿透?
- 一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。如果key对应的value一定是不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
- 如何解决?
- 1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
- 2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。(布隆表达式)
缓存雪崩
- 什么叫缓存雪崩?
- 当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
- 原因
- 批量key同时过期
- Redis不可用
- 冷启动没有预热
- 如何解决?
- 1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。(零点同时更新问题)
- 2:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
- 3:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)
缓存击穿
- 什么叫缓存击穿?
- 对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
- 缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
- 如何解决?
- 使用redis的setnx互斥锁先进行判断,(在指定的 key 不存在时,为 key 设置指定的值。)这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。
- 缓存数据永不过期,但后台异步更新数据,并在 Value 中存储逻辑过期时间
- 提前加载热点数据到缓存,并分散过期时间
并发竞争问题
多个系统同时操作(并发)Redis带来的数据问题
系统A、B、C三个系统,分别去操作Redis的同一个Key,本来顺序是1,2,3是正常的,但是因为系统A网络突然抖动了一下,B,C在他前面操作了Redis,这样数据就会出现问题
使用Zookeeper来解决多发竞争问题
- 某个时刻,多个系统实例都去更新某个 key。可以基于 Zookeeper 实现分布式锁。每个系统通过 Zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 Key,别人都不允许读和写。
Redis性能调优
- redis-cli -h 127.0.0.1 -p 6379 –intrinsic-latency 60 得到这个实例60S内的最大响应延迟
变慢的原因
使用复杂度过高的命令
- 设置慢日志的阈值,并查看slowlog
- 操作延迟的两种情况
- 经常使用 O(N) 以上复杂度的命令,Redis 在操作内存数据时,时间复杂度过高,要花费更多的 CPU 资源。
- Redis 一次需要返回给客户端的数据过多,更多时间花费在数据协议的组装和网络传输过程中。
操作bigkey
- 如果你查询慢日志发现,并不是复杂度过高的命令导致的,而都是 SET / DEL 这种简单命令出现在慢日志中,那么你就要怀疑你的实例否写入了 bigkey。
- redis-cli -h 127.0.0.1 -p 6379 –bigkeys -i 0.01
- 扫描bigKey -i 0.01 每一次扫描休息的时间
- 扫描结果中,对于容器类型(List、Hash、Set、ZSet)的 key,只能扫描出元素最多的 key。但一个 key 的元素多,不一定表示占用内存也多,你还需要根据业务情况,进一步评估内存占用情况
实例达到内存上限
- 当我们把 Redis 当做纯缓存使用时,通常会给这个实例设置一个内存上限 maxmemory,然后设置一个数据淘汰策略。而当实例的内存达到了 maxmemory 后,你可能会发现,在此之后每次写入新数据,操作延迟变大了。
- 解决方案
- 淘汰策略改为随机淘汰,随机淘汰比 LRU 要快很多(视业务情况调整)
- 拆分实例,把淘汰 key 的压力分摊到多个实例上
- 如果使用的是 Redis 4.0 以上版本,开启 layz-free 机制,把淘汰 key 释放内存的操作放到后台线程中执行(配置 lazyfree-lazy-eviction = yes)
Fork耗时严重
- ,操作 Redis 延迟变大,都发生在 Redis 后台 RDB 和 AOF rewrite 期间,那你就需要排查,在这期间有可能导致变慢的情况。
- 当 Redis 开启了后台 RDB 和 AOF rewrite 后,在执行时,它们都需要主进程创建出一个子进程进行数据的持久化。
主进程创建子进程,会调用操作系统提供的 fork 函数。
而 fork 在执行过程中,主进程需要拷贝自己的内存页表给子进程,如果这个实例很大,那么这个拷贝的过程也会比较耗时。 - 你可以在 Redis 上执行 INFO 命令,查看 latest_fork_usec 项,单位微秒。
- 解决方案
- 控制 Redis 实例的内存:尽量在 10G 以下,执行 fork 的耗时与实例大小有关,实例越大,耗时越久
- 合理配置数据持久化策略:在 slave 节点执行 RDB 备份,推荐在低峰期执行,而对于丢失数据不敏感的业务(例如把 Redis 当做纯缓存使用),可以关闭 AOF 和 AOF rewrite
- Redis 实例不要部署在虚拟机上:fork 的耗时也与系统也有关,虚拟机比物理机耗时更久
- 降低主从库全量同步的概率:适当调大 repl-backlog-size 参数,避免主从全量同步
开启内存大页
- 应用程序向操作系统申请内存时,是按内存页进行申请的,而常规的内存页大小是 4KB。Linux 内核从 2.6.38 开始,支持了内存大页机制,该机制允许应用程序以 2MB 大小为单位,向操作系统申请内存。
- 当 Redis 在执行后台 RDB 和 AOF rewrite 时,采用 fork 子进程的方式来处理。但主进程 fork 子进程后,此时的主进程依旧是可以接收写请求的,而进来的写请求,会采用 Copy On Write(写时复制)的方式操作内存数据。
- 但是请注意,主进程在拷贝内存数据时,这个阶段就涉及到新内存的申请,如果此时操作系统开启了内存大页,那么在此期间,客户端即便只修改 10B 的数据,Redis 在申请内存时也会以 2MB 为单位向操作系统申请,申请内存的耗时变长,进而导致每个写请求的延迟增加,影响到 Redis 性能。
- cat /sys/kernel/mm/transparent_hugepage/enabled 查看是否开启了内存大页,并关闭
AOF
- 当每一秒刷到一次到磁盘中时,当 Redis 后台线程在执行 AOF 文件刷盘时,如果此时磁盘的 IO 负载很高,那这个后台线程在执行刷盘操作(fsync系统调用)时就会被阻塞住。此时的主线程依旧会接收写请求,紧接着,主线程又需要把数据写到文件内存中(write 系统调用),但此时的后台子线程由于磁盘负载过高,导致 fsync 发生阻塞,迟迟不能返回,那主线程在执行 write 系统调用时,也会被阻塞住,直到后台线程 fsync 执行完成后,主线程执行 write 才能成功返回。
- 解决方案
- 当子进程在 AOF rewrite 期间,让后台子线程不执行刷盘(不触发 fsync 系统调用)操作。开启这个配置项,在 AOF rewrite 期间,如果实例发生宕机,那么此时会丢失更多的数据,性能和数据安全性,你需要权衡后进行选择。
- 从硬件层面来优化,更换为 SSD 磁盘,提高磁盘的 IO 能力,保证 AOF 期间有充足的磁盘资源可以使用。
使用了Swap
- cat /proc/$pid/smaps | egrep ‘^(Swap|Size)’ 查看Reds回否使用了Swap
- 解决方案
- 增加机器的内存,让 Redis 有足够的内存可以使用
- 整理内存空间,释放出足够的内存供 Redis 使用,然后释放 Redis 的 Swap,让 Redis 重新使用内存
碎片整理
网络带宽过载
Redis大key问题
- 定义标准
- 字符串类型(String):通常认为 Value 超过 10KB(高并发场景)或 1MB(普通场景)。
- 集合类型(Hash/List/Set/ZSet):元素数量超过 5000 个,或总内存占用超过 10MB。
- 其他场景:如集群中某个分片的内存占用显著高于其他节点。
- 主要影响
- 服务阻塞:单线程模型下,大 Key 的读写操作会阻塞主线程,导致客户端超时。
- 内存不均衡:集群模式下,大 Key 会导致数据分片的内存和 QPS 倾斜。
- 网络拥塞:大 Key 传输占用高带宽,可能打满网卡。
- 主从同步异常:大 Key 同步延迟可能引发主从数据不一致。
- 解决方案
- 数据拆分
- 按照业务逻辑拆分
- 按照时间/范围拆分
- 优化数据结构及存储
- 用 Bitmap 替代 String 记录布尔值(如用户签到)7。
- 用 HyperLogLog 替代 Set 统计独立访客
- 数据拆分
4Redis常见问题
https://x-leonidas.github.io/2022/02/01/05数据库/05-2非关系型数据库/redis/4Redis常见问题/