您现在的位置是:首页 >技术交流 >redis问题汇总网站首页技术交流
redis问题汇总
redis的优点
读写性能优异。十万/s的量级;
支持数据持久化。AOF,RDB
支持丰富的数据类型;
支持集群,可以实现主从复制,哨兵机制迁移,扩容等
缺点:
因为是基于内存的,所以虽然redis本身有key过期策略,但是还是需要提前预估和节约内存;因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
主从同步时可能会比较慢。在线扩容比较麻烦;
redis可能会有一些操作使它变慢;
redis应用场景:
高性能:
计数器:对于计数器这种频繁读写的很适合用快速的redis;用string类型
缓存,各种各样的缓存,这是它最重要的应用场景了。数据库的缓存减轻数据库压力;会话缓存保存session信息(string类型);全页缓存让你最快速度加载旧页面;
单线程:
分布式锁。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。
SETNX 命令不存在才插入。一般而言,还会对分布式锁加上过期时间。
SET lock_key unique_value NX PX 10000 (PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。
而解锁的过程就是将 lock_key 键删除,但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。
SETNX 命令的缺点是如果节点由于某些原因发生了主从切换,但是这个加锁的key还没有同步到slave节点。那么就会出现锁丢失的情况。正因为如此,Redis作者antirez基于分布式环境下提出了一种更高级的分布式锁的实现方式:**Redlock**
它的主要思想是设置**N个完全独立的主节点**,依次尝试从N个Master实例使用相同的key和随机值获取锁,当且仅当从大多数的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。获取锁失败,在所有Redis实例上进行解锁。我们的业务一般只需要一个Redis Cluster,或者一个Sentinel,但是这两者都不能承载RedLock的落地。如果你想要使用RedLock方案,还需要专门搭建一套环境。所以,如果不是对分布式锁可靠性有极高的要求(比如金融场景),不太建议使用RedLock方案
丰富的数据类型
bitmap实现二值信息的存储,HyperLogLog实现很小的内存统计大量数据(UV),GEO经纬度信息,Set 可以实现交集、并集等操作,从而实现共同好友等功能。ZSet 可以实现有序性操作,从而实现排行榜等功能。
redis为什么性能优异呢?
1、首先因为是基于内存的数据库,读取数据肯定比在硬盘快;
2、数据结构设计的好。主要是基于哈希表的而且有rehash来避免哈希冲突,大部分操作都是线性复杂度。当然一些getall,交并集不是线性的。这一点后面会具体说。
3、采用单线程加IO阻塞的网络模型。单线程避免了不必要的上下文切换和竞态消耗,不需要加锁了。(注意这里的单线程指的是读写,一些辅助比如持久化是需要另外开一个线程的),IO复用大大提高了效率。
下面说一下redis的基本数据类型和数据结构。
整体来看,有一个全局哈希表,通过这个表,就可以找到对应的键值对,有渐进rehash来避免哈希冲突。然后其中的值的类型很多也是基于哈希表的。因此导致大部分操作都是O(1)复杂度的。
string类型
底层是SDS简单字符串,(二进制方式保存,有长度字段,拼接时会检查防止溢出)
有int,emdstr,raw三种编码方式。int是保存整形数据的,字符串对象的ptr会将void*转换成 long;
emdstr保存短的字符串,这样字符串对象和SDS分配一块连续的内存;
raw保存长字符串,字符串对象后ptr指向SDS。
(emdstr不能更改,要更改实际上要转换成raw类型的)
List 类型的底层数据结构是由双向链表或压缩列表。
(压缩列表实际上是数组形式,只不过表头有列表长度,尾部偏移量,项数,表尾有结束标志,查询第一个最后一个元素比较快,查中间的慢)
list主要的应用有消息队列。list天然满足消息的顺序性,BRPOP可以阻塞式读取,防止空队列还在读。
重复消息处理可以实现全局ID,然后处理过的ID保存起来;List 类型提供了 BRPOPLPUSH 命令,这个命令的作用是让消费者程序从一个 List 中读取消息,同时,Redis 会把这个消息再插入到另一个 List(可以叫作备份 List)留存。实现可靠性。
List缺点就是一条信息只能一个消费者读取。后来版本的有STREAM类型,实现消费组读取。
不过消息队列有更好的组件kafka,rabitMQ等
Hash 是一个键值对(key - value)集合,其中 value 的形式如: value=[{field1,value1},…{fieldN,valueN}]。Hash 特别适合用于存储对象比如购物车。
Hash 类型的底层数据结构是由压缩列表或哈希表实现的
Set 类型是一个无序并唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储。所以 Set 类型除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。Set 类型的底层数据结构是由哈希表或整数集合实现的:
主要用于点赞统计,共同关注数量,抽奖活动。集合操作最好给从库或者客户端进行,因为复杂度比较高。
Zset 类型相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。
Zset 类型的底层数据结构是由压缩列表或跳表实现的:
有序集合比较典型的使用场景就是排行榜
跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位。可以看到,这个查找过程就是在多级索引上跳来跳去,最后定位到元素。这也正好符合“跳”表的叫法。当数据量很大时,跳表的查找复杂度就是 O(logN)。(其实是空间换时间的做法,而且链表必须是有序的)
bitmap,hyperloglog,GEO等
问题:整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,那为什么 Redis 还会把它们作为底层数据结构呢?
**内存利用率,数组和压缩列表都是非常紧凑的数据结构,**它比链表占用的内存要更少。Redis是内存数据库,大量数据存到内存中,此时需要做尽可能的优化,提高内存的利用率。
数组对CPU高速缓存支持更友好,所以Redis在设计时,集合数据元素较少情况下,默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度。当数据元素超过设定阈值后,避免查询时间复杂度太高,转为哈希和跳表数据结构存储,保证查询效率。
(可以总结数据结构设计的精巧了,首先整体上有全局哈希表快速定位键值对的位置。然后值有很多集合类型,很多也是基于哈希表的。比如set,hash。跳表也可以实现logn的访问。
同时,为了节省内存,有压缩列表和整数集合的形式,并且数组对cache友好,所以数据元素少时可以用数组形式,不及能节省内存,并且cache友好,不会降低查询速度。复杂度都是在大量数据才能体现的)
redis为什么用单线程?
注意一点,Redis是单线程,主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
主要原因是因为:CPU不是瓶颈所在,因为是基于内存操作的,而且数据结构设计合理,复杂度很低的。因此,IO复用+单线程比多线程更适合。创建切换线程是需要时间的,如果很简单,一个熟练工自己做就可以,分发给很多人做需要时间,可能分发的时间代价比自己处理这一个连接请求还大。如果处理过程复杂一点,代价比创建切换线程时间大,那么可以多线程。
(体现了基准测试,测量才能提高。要看看分发时间和处理时间比较。以及和连接时间比较)
2、而且多线程不可避免会有数据竞态消耗,存在加锁的消耗以及死锁消耗等等问题,而且对于上层编码来说,多线程编程加锁更复杂了。
3、从内存看,创建线程需要内存的10M左右,redis很有可能有很多并发连接,因此内存消耗也大,并且redis本来内存资源就比较珍贵。
瓶颈可能是内存大小或者网络带宽。提高性能不如增加网络带宽或者内存。
为什么redis4.0之后又引入了多线程?
这主要是为了充分利用多核CPU的能力,主要用于特别大的高并发的场景,并发量一大,CPU可能就成为瓶颈了,因为处理需要排队,排队时延可能比较长。主线程用来接收客户端的连接,然后分配给工作线程(多个CPU)同时处理并返回结果。
持久化机制
通过AOF日志和RDB快照技术。
AOF只记录写命令操作,不记录读命令操作。重启的时候,执行一下AOF中的命令。就相当于恢复数据了。
这里涉及到两个问题。一个是写回策略一个是AOF重写。
AOF写操作执行成功但是写AOF失败(没来得及写回硬盘就宕机了),数据丢失;
AOF写操作会给下一个命令阻塞,因为是在一个进程执行的。
AOF具体来说:(先写到server.aof_buf 用户态缓冲区、写到AOF文件拷贝到了内核缓冲区 page cache、写入硬盘)
1 Redis 执行完写操作命令后,会将命令追加到 server.aof_buf 缓冲区;
2 然后通过 write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区 page cache,等待内核将数据写入硬盘;
3 具体内核缓冲区的数据什么时候写入到硬盘,由内核决定。Redis 提供了 3 种写回硬盘的策略,控制的就是上面说的第三步的过程。
always:最大程度减少数据丢失,但是每次都要写回,性能影响很大。写完就调用fsyc()函数;
No 策略的话,是交由操作系统来决定何时将 AOF 日志内容写回硬盘,相比于 Always 策略性能较好,但是操作系统写回硬盘的时机是不可预知的,如果 AOF 日志内容没有写回硬盘,一旦服务器宕机,就会丢失不定数量的数据。不调用fsyc()
Everysec 策略的话,是折中的一种方式。创建一个异步任务来实现fsyc()
Redis 为了避免 AOF 文件越写越大,提供了 AOF 重写机制(涉及到子进程、重写缓冲区)
重写简单来说就是多条命令合并成一条命令(从数据库读最新的值,把AOF中涉及该项的写删除),写到新的AOF文件再覆盖原文件。覆盖原因是因为怕重写失败造成污染;
由于重写需要读取所有缓存的键值对数据,所以一般不放在主进程中,而是由后台子进程 bgrewriteaof 来完成的,这么做可以达到两个好处:子进程进行 AOF 重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程;为什么不用线程?因为共享内存,需要加锁保证数据安全,降低性能。
另外还有写时复制的优化技术,子进程只复制父进程的task_struct和页表(虚拟地址空间),不复制物理内存,子进程修改再重新映射就可以。节约物理内存,节约fork的时间。
还有一个问题就是子进程在进行 AOF 重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的 AOF 文件所保存的数据库状态不一致
Redis 服务器设置了一个 AOF 重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 Redis 服务器执行完一个写命令之后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。重写完成,把AOF 重写缓冲区的数据添加到aof中。
(AOF三部曲、写回策略、AOF重写以及子进程、写时复制、AOF重写缓冲区保证数据一致)
RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据。因此在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave,他们的区别就在于是否在「主线程」里执行:
执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程;执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞;
RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。
这就是 RDB 快照的缺点,在服务器发生故障时,丢失的数据会比 AOF 持久化的方式更多,因为 RDB 快照是全量快照的方式,因此执行的频率不能太频繁,否则会影响 Redis 性能,而 AOF 日志可以以秒级的方式记录操作命令,所以丢失的数据就相对更少。
比较:RDB因为是实际的数据,恢复起来更快;而由于是全量的数据所以不可能频繁使用一般几十分钟一天,所以如果数据丢失的话也会丢失更多,而AOF可以设置写回策略,所以丢失地少,但是AOF性能差一点,因为写AOF会阻塞下一个命令,RDB只是创建子进程。
所以有混合持久化。新的AOF文件前半段是RDB格式的全量数据(AOF缓冲区的数据)后半段是AOF格式的增量数据(重写缓冲区的数据)
一些大key对于AOF写回磁盘会阻塞下一个命令,AOF重写fork子进程复制页表也会阻塞、写时复制拷贝物理内存也会阻塞。网络流量也大,阻塞。
过期删除和内存淘汰策略
这两个都是为了内存资源有限服务的。
expire key ttl key persist key
定时删除:一到过期时间就删除,对内存最好但是性能较差
惰性删除:不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。
定期删除:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。
所以, Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。
内存淘汰主要是分为在设置过期时间数据淘汰和所有数据淘汰。又分为最早过期,随机,lru,lfu。
4.0之后用lfu,因为lru有问题。
需要用链表管理所有的缓存数据,这会带来额外的空间开销;当有数据被访问时,需要在链表上把该数据移动到头端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能。(其实用hash+双向链表可以实现不耗时);
缓存污染问题,比如应用一次读取了大量的数据,而这些数据只会被读取这一次,那么这些数据会留存在 Redis 缓存中很长一段时间,造成缓存污染。(这个问题在linux有活跃非活跃lru, mysql也是类型的思想)
LFU 算法是根据数据访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。可以解决缓存污染。
数据库和缓存的一致性怎么保证? 主从节点一致性怎么保证?
由于引入了redis缓存,那么在数据更新时,不仅要更新数据库,而且要更新缓存,这两个更新操作存在前后的问题:
无论是「先更新数据库,再更新缓存」,还是「先更新缓存,再更新数据库」,这两个方案都存在并发问题,当两个请求并发更新同一条数据的时候,可能会出现缓存和数据库中的数据不一致的现象。
用旁路缓存策略解决
先更新数据库,再删除缓存。 读的时候如果缓存有,就直接读,没有就加载到缓存。为了确保万无一失,还给缓存数据加上了「过期时间」,就算在这期间存在缓存数据不一致,有过期时间来兜底,这样也能达到最终一致。
如何保证「先更新数据库 ,再删除缓存」这两个操作能执行成功?
**重试机制。**延时双删
如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。
订阅 MySQL binlog,再操作缓存。
「先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 binlog 里。
于是我们就可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除,阿里巴巴开源的 Canal 中间件就是基于这个实现的。Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。
主从模式相关?
持久化无法解决单点故障(服务器宕机或者硬盘损坏)。而集群需要解决数据一致性问题。
Redis 提供了主从复制模式,来避免上述的问题。这个模式可以保证多台服务器的数据一致性,且主从服务器之间采用的是「读写分离」的方式。主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。
主从同步机制比较复杂,分成三个阶段。(先执行 replicaof 命令确定主从)
第一阶段:建立连接,协商同步。执行了 replicaof 命令后,从服务器就会给主服务器发送 psync 命令,表示要进行数据同步。psync 命令包含两个参数,分别是主服务器的 runID 和复制进度 offset。(第一次ID是?,offset是-1)。然后主服务器收到 psync 命令后,会用 FULLRESYNC 作为响应命令返回给对方。并且这个响应命令会带上两个参数:主服务器的 runID 和主服务器目前的复制进度 offset。从服务器收到响应后,会记录这两个值。
所以,第一阶段的工作时为了全量复制做准备。
第二阶段:主服务器同步数据给从服务器。主服务器会执行 bgsave 命令来生成 RDB 文件,然后把文件发送给从服务器。从服务器收到 RDB 文件后,会先清空当前的数据,然后载入 RDB 文件。
第三阶段:主服务器发送新写操作命令给从服务器。完成 RDB 的载入后,会回复一个确认消息给主服务器。
mysql也有主从复制(主节点dump线程发送binlog,从节点开启IO线程接收放到relay日志,然后SQL线程执行)
接着,主服务器将 replication buffer 缓冲区里所记录的写操作命令发送给从服务器,从服务器执行来自主服务器 replication buffer 缓冲区(包括主服务器生成RDB期间,发送给从服务器期间,从服务器接收期间)里发来的命令,这时主从服务器的数据就一致了。
主从服务器在完成第一次同步后,双方之间就会维护一个 TCP 连接。后续主服务器可以通过这个连接继续将写操作命令传播给从服务器,然后从服务器执行该命令,使得与主服务器的数据库状态相同。
如何分摊主服务器同步时的压力?
代理服务器:从服务器可以有自己的从服务器;
增量复制:如果遇到网络断开,增量复制就可以上场了,不过这个还跟repl_backlog_size环形缓冲区,记录了主服务器写的偏移和从服务器读的偏移。
主从节点数据不一致问题? 主从同步的数据丢失不可避免,统一持久化的数据丢失也不可避免,性能和可靠互斥的
在主从节点命令传播阶段,主节点收到新的写命令后,会发送给从节点。但是,主节点并不会等到从节点实际执行完命令后,再把结果返回给客户端,而是主节点自己在本地执行完命令后,就会向客户端返回结果了。如果从节点还没有执行主节点同步过来的命令,主从节点间的数据就不一致了。
如何避免?其实没办法完全避免的。这种强一致性就要用到raft协议了。只能从应用层面来限制。比如保持网络良好;开发一个监控 程序监控主从节点间的复制进度,差很多的话就停止服务;
哨兵机制
主节点挂了 ,从节点是无法自动升级为主节点的,这个过程需要人工处理,在此期间 Redis 无法对外提供写操作。
此时,Redis 哨兵机制就登场了,哨兵在发现主节点出现故障时,由哨兵自动完成故障发现和故障转移,并通知给应用方,从而实现高可用性。
它会监测主节点是否存活,如果发现主节点挂了,它就会选举一个从节点切换为主节点,并且把新主节点的相关信息通知给从节点和客户端。(监控,选主,通知)
监控:哨兵会每隔 1 秒给所有主从节点发送 PING 命令,当主从节点收到 PING 命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否在正常运行。如果主节点或者从节点没有在规定的时间内响应哨兵的 PING 命令,哨兵就会将它们标记为「主观下线」。
但是是因为有可能「主节点」其实并没有故障,可能只是因为主节点的系统压力比较大或者网络发送了拥塞,导致主节点没有在规定时间内响应哨兵的 PING 命令。为了减少误判的情况,哨兵在部署的时候不会只部署一个节点,而是用多个节点部署成哨兵集群(最少需要三台机器来部署哨兵集群),通过多个哨兵节点一起判断。其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。哨兵判断完主节点客观下线后,哨兵就要开始在多个「从节点」中,选出一个从节点来做新主节点。
选主选出leader哨兵实现选主
某个哨兵判定主节点客观下线后,该哨兵就会发起投票,告诉其他哨兵,它想成为 leader,想成为 leader 的哨兵节点,要满足两个条件:第一,拿到半数以上的赞成票;第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。
转移:(过程和k8s调度选服务器也类似的)
第一:选出新的主节点。首先要排除离线的,网络不好的节点;再根据优先级、复制进度、ID 号选出最适合的。
然后,所有从节点的主节点信息修改;通过发布订阅模式通知客户端变化;旧的主节点上线后让他成为新的从节点;
切片集群相关
从3.0开始,官方提供了一个名为Redis Cluster的方案,用于实现切片集群。一个redis实例数据太多,会造成很多问题(哈希冲突,AOF持久化fork耗时大等)
具体来说,Redis Cluster方案采用哈希槽(Hash Slot,接下来我会直接称之为Slot),来处理数据和实例之间的映射关系。
哈希槽和实例的映射关系,redis会自动分配,平均分配的原则。
Redis实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。
还要解决的一个问题就是如何集群变化了,比如实例加减,为了负载均衡,哈希槽要重新分配。这时候客户端再请求就找不到数据了。
这时候。实例会给客户端发送MOVED指令,带着最新的哈希槽在哪个实例上的信息,重定向后就可以找到了。
如果实例正在迁移。会发ASKING,会让数据迁移完成再读取数据。
redis变慢如何排查
redis有一些阻塞点,需要了解
1、客户端阻塞点:全量查询操作 HGETALL、LRANGE,SMEMBERS,以及集合的聚合统计操作,例如求交、并和差集。这些操作可以作为 Redis 的第一个阻塞点:集合全量查询和聚合操作。
删除操作也是潜在的阻塞点。应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配 那么,什么时候会释放大量内存呢?其实就是在删除大量键值对数据的时候,最典型的就是删除包含了大量元素的集合,也称为 bigkey 删除
2、和磁盘交互时的阻塞点(持久化)
AOF日志同步写回时会阻塞下一步的命令。同时如果大量数据fork子进程页表的时候也会阻塞。
3、主从节点交互时的阻塞点。从库同步主库信息时,先要删除自己的全部数据(删除需要时间),再次RDB加载到内存运行也要时间,RDB文件越大越慢。
4、CPU结构
使用多核CPU时,可以给Redis实例绑定物理核;使用NUMA架构时,注意把Redis实例和网络中断处理程序运行在同一个CPU Socket上。
如何排查?
首先看看是不是网络带宽、以及基线性能和运行时延迟测试。
是否用了慢查询命令?slowlog get 10这个命令可以看;
是否存在bigkey?(scan操作减缓延迟)
AOF配置级别是什么?业务层面是否的确需要这一可靠性级别?
Redis实例的内存使用是否过大?发生swap了吗?如果是的话可以使用切片集群或者增加内存;
是否运行了Redis主从集群?如果是的话,把主库实例的数据量大小控制在2~4GB,以免主从复制时,从库因加载大的RDB文件而阻塞。
是否使用了多核CPU或NUMA架构的机器运行Redis实例?使用多核CPU时,可以给Redis实例绑定物理核;使用NUMA架构时,注意把Redis实例和网络中断处理程序运行在同一个CPU Socket上。
首先查后端业务代码。是否有长时间循环,等待临界资源等。
(先基线测试,排除网络异常?内存是不是不够?;慢日志查看,是否有bigkey,AOF持久化策略、主从复制RDB文件大小限制,CPU架构绑定物理核)
缓存击穿,穿透,雪崩
缓存穿透:缓存和数据库都没有。可能是业务误操作或者黑客恶意攻击。
解决:1.非法输入限制。API处就要检查字段是否合法; 2、缓存空值或者默认值,设置一个过期时间,这样一段时间内就不会访问数据库了;3 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在。(最新的布谷鸟过滤器可以解决布隆过滤器删除麻烦)
缓存雪崩
当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增。
解决办法: 1、均匀设置过期时间;2、互斥锁保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里);雪崩发生后进行限流和服务熔断。
缓存击穿
我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。你可以认为缓存击穿是缓存雪崩的一个子集。
解决方案也是互斥锁或者不给热点数据设置过期时间,后台线程来更新缓存。