目录
十、pipeline 有什么好处,为什么要用 pipeline ?
十七、是否使用过Redis Cluster集群,集群的原理是什么?
十八、Redis Cluster集群方案什么情况下会导致整个集群不可?
二十一、假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是 以某个固定的已知的前缀开头的,如果将它们全部找出来?
二十二、如果有大量的 key 需要设置同一时间过期,一般需要注意什么吗?
二十七、缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
一、为什么要用缓存
使用缓存的目的就是提升读写性能。而实际业务场景下,更多的是为了提升读性能,带来更好的性 能,带来更高的并发量。 Redis 的读写性能比 Mysql 好得多,我们就可以把 Mysql 中的热点数据 缓存到 Redis 中,提升读取性能,同时也减轻了 Mysql 的读取压力。
二、使用 Redis有哪些好处
读取速度快,因为数据存在内存中,所以数据获取快;
支持多种数据结构,包括字符串、列表、集合、有序集合、哈希等;
支持事务,且操作遵守原子性,即对数据的操作要么都执行,要么都不支持;
还拥有其他丰富的功能,队列、主从复制、集群、数据持久化等功能
三、什么是 redis?
Redis 是一个开源(BSD 许可)、基于内存、支持多种数据结构的存储系统,可以作为数据库、 缓 存和消息中间件。它支持的数据结构有字符串(strings)、哈希(hashes)、列表(lists)、 集合 ( sets)、有序集合(sorted sets)等,除此之外还支持 bitmaps、hyperloglogs 和地理 空间( geospatial )索引半径查询等功能。 它内置了复制(Replication)、 LUA 脚本(Lua scripting)、 LRU 驱动事件(LRU eviction)、 事 务(Transactions)和不同级别的磁盘持久化(persistence)功能,并通过 Redis 哨兵(哨 兵)和 集群(Cluster)保证缓存的高可用性(High availability)。
四、redis和memcached区别
Redis和Memcached的主要区别在于数据持久化、数据类型、线程模型、内存使用效率、数据操作性能、数据备份能力、系统扩展性以及数据安全性。
-
数据持久化:Redis支持将数据从内存持久化到硬盘,通过RDB快照和AOF日志两种主要策略实现持久化,而Memcached不支持数据持久化操作,服务器重启后数据会丢失。
-
数据类型:Redis支持多种数据结构类型,如列表、集合和哈希表等,而Memcached主要在内存中维护一张大型哈希表进行数据的存储,仅支持基本的键值对数据类型。
-
线程模型:Memcached采用多线程模型处理请求,基于IO多路复用技术,而Redis采用单线程模型处理请求,虽然Redis 6.0后引入了多线程处理性能有所提高,但基本操作仍以单线程为主。
-
内存使用效率:对于简单的键值对存储,Memcached在内存利用率上具有优势。但如果Redis使用哈希结构来存储键值对数据,其由于使用了组合式的压缩,可能使得内存利用率超过Memcached。
-
数据操作性能:在数据的读取操作上,Redis和Memcache的性能大致相当。但在处理小数据时,Redis的性能可能略高于Memcached,而在处理大数据时,Memcached的性能可能优于Redis。
-
数据备份能力:Redis具备数据备份的能力,通过其主从备份模式实现。相反,Memcached因其不支持数据持久化,从而无法实现数据的备份。
-
系统扩展性:两者都需要通过集群的方式进行扩展,包括主从模式和哈希模式。但在实现更高的处理能力方面,Redis和Memcached的具体扩展方式有所不同。
-
数据安全性:由于Memcached把所有数据存储在内存中,当服务器发生故障并重启后,存储在其中的数据会完全丢失。而Redis能够将数据周期性地保存到硬盘,以实现持久化存储,保证在必要时数据的可恢复性。
综上所述,Redis和Memcached各有其优势和适用场景。如果需要数据持久化、支持多种数据结构、高可用性和可扩展性,Redis是更好的选择。而如果需要简单的高速缓存系统,对数据持久化和复杂的数据结构需求不高,Memcached可能更适合。
五、为什么redis单线程模型效率也能那么高
1. C 语言实现,效率高
2. 纯内存操作
3. 基于非阻塞的 IO 复用模型机制
4. 单线程的话就能避免多线程的频繁上下文切换问题
5. 丰富的数据结构(全称采用 hash 结构,读取速度非常快,对数据存储进行了一些优化,比如亚索表,跳表等)
六、redis的线程模型
Redis 基于 Reactor 模式开发了自己的网络事件处理器 - 文件事件处理器(file event handler,后文简称为 FEH),而该处理器又是单线程的,所以 redis 设计为单线程模型。
文件事件分派器从队列中接收 I/O 多路复用程序传来的socket,并根据socket产生的事件类型,调用相应的事件处理器。当上一个socket产生的事件被对应事件处理器执行完后,文件事件分派器才会向队列拉取下一个要处理的socket,保证socket操作一定不会并发的执行,保证线程安全。Redis所谓的单线程并不是所有工作都是只有一个线程在执行,而是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理。
这就是所谓的“单线程”。这也是Redis对外提供键值存储服务的主要流程。由于Redis在处理命令的时候是单线程作业的,所以会有一个Socket队列,每一个到达的服务端命令来了之后都不会马上被执行,而是进入队列,然后被线程的事件分发器逐个执行。
文件事件处理器的几个组成部分:
七、redis 6.0 引入多线程
redis处理接受客户端请求后,可以简化为三部分操作:
- read 读区请求
- parse->resp>command excute 解析请求到命令执行
- write 相应请求
redis 6.0之前,以上三个步骤依次执行
6.0之后,read和write两部分支持多线程,第二部处理指令依然是单线程。
6.0版本优化之后,主线程和多线程网络IO的执行流程如下:
具体步骤如下:
- 主线程建立连接,并接受数据,并将获取的 socket 数据放入等待队列;
- 通过轮询的方式将 socket读取出来并分配给 IO 线程;
- 之后主线程保持阻塞,一直等到 IO 线程完成 socket 读取和解析;
- I/O 线程读取和解析完成之后,返回给主线程 ,主线程开始执行 Redis 命令;
- 执行完Redis命令后,主线程阻塞,直到IO 线程完成 结果回写到socket 的工作;
- 主线程清空已完成的队列,等待客户端新的请求。
本质上是将主线程 IO 读写的这个操作 独立出来,单独交给一个I/O线程组处理。
这样多个 socket 读写可以并行执行,整体效率也就提高了。同时注意 Redis 命令还是主线程串行
这里举个生活中的例子展示IO读写多线程如何提高效率的:
车站买票和上车,买票对应IO读写,上车对应redis操作
假设没有多线程,也就是相当于只有一个买票窗口,那么人太多,买票窗口就会聚集大堆人,一个人买到票才轮到下一个,买票时间随着人数的增多而增多,多线程就是多开几个窗口,解决买票排队情况,降低大家总的买票时间。可能你会问,上车不还是一个一个按顺序上吗,哪怕你全部人一下子买好票了,我上车的速度还是不变啊?对,但上车几乎是不花费时间的(基于内存),也就是不管你现在是1个人买票了,还是1000个人买票了,都可以秒上车,所以买票和上车的瓶颈在买票,提高买票效率则提高整体效率。
写操作支持多线程
// 这里说有三个IO 线程,还有一个线程是main线程,main线程负责IO读写和命令执行操作
io‐threads 4
读操作支持多线程
// 将支持IO线程执行 读写任务。
io‐threads‐do‐reads yes
Redis6.0 为什么要引入多线程呢?
- 可以充分利用服务器CPU的多核资源,而主线程明显只能利用一个
- 多线程任务可以分摊 Redis 同步 IO 读写负荷,降低耗时
Redis 将所有数据放在内存中,内存的响应时长大约为 100 纳秒,对于小数据包,Redis 服务器可以处理 80,000 到 100,000 QPS,这也是 Redis 处理的极限了,对于 80%的公司来说,单线程的 Redis 已经足够使用了。
随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的 QPS. 常见解决方案是分布式架构中对数据进行分区并采用多个服务器。这种方式: 维护代价大;某些命令失效;数据分区无法解决热点读/写问题,数据偏斜,重新分配和放大/缩小变得更加复杂等等。
从 Redis 自身角度来说,因为读写网络的 read/write 系统调用占用了 Redis执行期间大部分 CPU 时间,瓶颈主要在于网络的 IO 消耗. redis6.0充分利用多核cpu的能力分摊 Redis 同步 IO 读写负荷。
八、为什么Redis需要把所有数据放到内存中?
Redis 将数据放在内存中有一个好处,那就是可以实现最快地对数据读取,如果数据存储在硬盘 中,磁盘 I/O 会严重影响 Redis 的性能。而且 Redis 还提供了数据持久化功能,不用担心服务 器重 启对内存中数据的影响。其次现在硬件越来越便宜的情况下,Redis 的使用也被应用得越来 越多, 使得它拥有很大的优势
九、Redis 的同步机制了解是什么?
Redis 支持主从同步、从同步。如果是第一次进行主从同步,主节点需要使用 bgsave 命令,再将 后续修改操作记录到内存的缓冲区,等 RDB 文件全部同步到复制节点,复制节点接收完成后将 RDB 镜像记载到内存中。等加载完成后,复制节点通知主节点将复制期间修改的操作记录同步到 复 制节点,即可完成同步过程
十、pipeline 有什么好处,为什么要用 pipeline ?
使用 pipeline(管道)的好处在于可以将多次 I/O 往返的时间缩短为一次,但是要求管道中执行 的 指令间没有因果关系。 用 pipeline 的原因在于可以实现请求/响应服务器的功能,当客户端尚未读取旧响应时,它也可 以 处理新的请求。如果客户端存在多个命令发送到服务器时,那么客户端无需等待服务端的每 次响应 才能执行下个命令,只需最后一步从服务端读取回复即可。
十一、redis缓存刷新策略有哪些?
十二、redis写的最佳实践
一致性问题:
在Redis的key值未过期的情况下,用户修改了个人信息,我们此时既要操作数据库数据,也要操作Redis数据。从本质上讲,无论是先写数据库还是先写缓存,都是为了保证数据库和缓存的数据一致,也就是我们常说的数据一致性。
操作缓存和数据库时需要考虑的三个问题:
1. 删除缓存还是更新缓存?
更新缓存:每次更新数据库都更新缓存,无效写操作较多 删除缓存:更新数据库时让缓存失效,查询时再更新缓存 结论:推荐直接使用「删除」操作。
2. 如何保证缓存与数据库的操作的同时成功或者失败
对于单体系统:将缓存与数据库操作放在一个事务中 对于分布式系统:利用TCC等分布式事务方案
3. 先操作缓存还是先操作数据库?
3-1. 先删除缓存,再操作数据库
这种方式可能存在以下两种异常情况: 1. 删除缓存失败,这时可以通过程序捕获异常,直接返回结果,不再继续更新数据库,所以不会出现数据不一致的问题 2. 删除缓存成功,更新数据库失败。在多线程下可能会出现数据不一致的问题
这时,Redis中存储的旧数据,数据库的值是新数据,导致数据不一致。这时我们可以采用 延时双删 的策略,即更新数据库数据之后,再删除一次缓存。
用伪代码表示就是:
/**
* 延时双删
* @author*/
public void update(String key, Object data) {
// 首先删除缓存
redisCache.delKey(key);
// 更新数据库
db.updateData(data);
// 休眠一段时间,时间依据数据的读取耗费的时间而定
Thread.sleep(500);
// 再次删除缓存
redisCache.delKey(key);
}
3-2. 先操作数据库,再删除缓存
这种方式可能存在以下两种异常情况: 1. 更新数据库失败,这时可以通过程序捕获异常,直接返回结果,不再继续删除缓存,所以不会出现数据不一致的问题 2. 更新数据库成功,删除缓存失败。导致数据库是最新数据,缓存中的是旧数据,数据不一致 这里, 我们有两种方式来解决数据不一致问题:失败重试 和 异步更新。
方式1. 失败重试
如果删除缓存失败,我们可以捕获这个异常,把需要删除的 key 发送到消息队列。自己创建一个消费者消费,尝试再次删除这个 key,直到删除成功为止。
这种方式有个缺点,首先会对业务代码造成入侵,其次引入了消息队列,增加了系统的不确定性。
方式2. 异步更新
因为更新数据库时会往 binlog 中写入日志,所以我们可以启动一个监听 binlog变化的服务(比如使用阿里的 canal开源组件),然后在客户端完成删除 key 的操作。如果删除失败的话,再发送到消息队列。
总之,对于删除缓存失败的情况,我们的做法是不断地重试删除操作,直到成功。无论是重试还是异步删除,都是最终一致性的思想。
十三、Redis持久化方式有哪些?以及有什么区别?
在Redis中,有两种常见的持久化方式 RDB(Redis Database)和AOF(Append-Only File)。它们都用于将Redis中的数据持久化到磁盘上,以便在Redis重启后能够恢复数据。
RDB持久化方式:
RDB是Redis默认的持久化方式,它会将Redis在某个时间点的数据以二进制格式保存到磁盘上。RDB持久化方式通过fork一个子进程来完成持久化操作,该子进程会将数据写入到一个临时文件中,当持久化完成后,再用这个临时文件替换原来的RDB文件。RDB文件是一个紧凑的二进制文件,它保存了Redis在某个时间点的数据快照。
优点:
RDB文件紧凑,占用的磁盘空间相对较小。
RDB文件的恢复速度比AOF方式快,适用于大规模的数据恢复。
缺点:
RDB方式是定期保存数据快照,如果Redis在定期保存之前发生故障,可能会丢失最后一次快照之后的数据。
RDB方式需要fork一个子进程来进行持久化操作,如果数据量较大,fork操作可能会导致Redis的性能下降。
AOF持久化方式:
AOF持久化方式会将Redis的写操作以日志的形式追加到文件中(Append-Only File),当Redis重启时,会重新执行这些写操作来恢复数据。AOF文件是一个文本文件,它以易读的方式记录了Redis的写操作。
优点:
AOF文件记录了Redis的所有写操作,可以确保数据的完整性和持久性。
AOF文件是一个文本文件,易于阅读和理解。
缺点:
AOF文件相对于RDB文件来说,占用的磁盘空间较大。
AOF文件的恢复速度比RDB方式慢,适用于小规模的数据恢复。
在实际应用中,可以根据需求选择适合的持久化方式。如果对数据的完整性和持久性要求较高,可以选择AOF方式;如果对磁盘空间和数据恢复速度要求较高,可以选择RDB方式;也可以同时使用RDB和AOF方式进行持久化,以兼具数据恢复速度和数据完整性的优势。
下面是一个使用Java操作Redis的示例代码,演示了如何配置和使用RDB和AOF持久化方式:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisPersistenceExample {
public static void main(String[] args) {
// 创建Jedis连接池
JedisPoolConfig config = new JedisPoolConfig();
JedisPool jedisPool = new JedisPool(config, "localhost", 6379);
// 从连接池中获取Jedis实例
try (Jedis jedis = jedisPool.getResource()) {
// 设置RDB持久化方式
jedis.configSet("save", "3600 1"); // 每隔3600秒,如果至少有1个key发生变化,则保存RDB快照
// 设置AOF持久化方式
jedis.configSet("appendonly", "yes"); // 开启AOF持久化
// 执行Redis操作
jedis.set("key1", "value1");
jedis.set("key2", "value2");
jedis.set("key3", "value3");
// 保存RDB快照
jedis.save();
// 执行Redis重启操作(模拟Redis重启)
jedis.flushAll(); // 清空数据
// 恢复RDB快照
jedis.restore("key1", 0, jedis.dump("key1")); // 恢复key1的值
// 查看恢复后的数据
System.out.println(jedis.get("key1")); // 输出:value1
// 查看AOF文件路径
System.out.println(jedis.configGet("dir").get(1) + "/" + jedis.configGet("appendfilename").get(1));
}
// 关闭Jedis连接池
jedisPool.close();
}
}
十四、Redis实现消息队列?
一般使用 list 结构作为队列,lpush生产消息, rpop消费消息。当 rpop 没有消息的时候,要适当sleep一会再重试。
可不可以不用 sleep 呢?
list 还有个指令叫brpop,在没有消息的时候,它会阻塞住直到消息到来。
能不能生产一次消费多次呢?
使用 pub / sub 主题订阅者模式,可以实现 1:N 的消息队列。
pub / sub 有什么缺点?
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列。
Redis 如何实现延时队?
使用 sortedset ,拿时间戳作为 score ,消息内容作为 key 调用 N 秒之前的数据轮询进行处理。
十五、Redis事务
什么是 redis事务?原理是什么?
Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位。它可以保证一次执行多个命令, 每个事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地执行。服务端在执行 事务 的过程中,不会被其他客户端发送来的命令请求打断。 它的原理是先将属于一个事务的命令发送给 Redis ,然后依次执行这些命令。
redis事务的注意点有哪些?
需要注意的点有:
Redis 事务是不支持回滚的,不像 MySQL 的事务一样,要么都执行要么都不执行;
Redis 服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断。直到事务命令全部执行完毕才会执行其他客户端的命令。
redis事务为什么不支持回滚?
Redis 的事务不支持回滚,但是执行的命令有语法错误,Redis 会执行失败,这些问题可以从程序 层 面捕获并解决。但是如果出现其他问题,则依然会继续执行余下的命令。这样做的原因是回滚 需要增加很多工作,而不支持回滚则可以保持简单、快速的特性。
十六、熟悉哪些Redis集群模式?
1.Redis Sentinel
体量较小时,选择单机模式
2.Redis Cluster
官方提供的集群化方案,体量较大时,选择Cluster,通过分片,使用更多内存
3.Twemprox
Twemprox是Twtter开源的一个Redis和Memcached代理服务器,主要用于管理Redis和Memcached集群,减少与 Cache 服务器直接连接的数量。
4.Codis
Codis 是一个代理中间件,当客户端向Codis发送指令时, Codis负责将指令转发到后面的Redis来执行,并将结果返回给客户端。 一个Codis实例可以连接多个Redis 实例,也可以启动多个Codis实例来支撑,每个Codis节点都是对等的,这样可以增加整体的QPS, 起到容灾功能。
5.客户端分片
现在基本很少使用,自己在业务代码层实现。
十七、是否使用过Redis Cluster集群,集群的原理是什么?
使用过 Redis 集群,它的原理是:
- 所有的节点相互连接
- 集群消息通信通过集群总线通信,集群总线端口大小为客户端服务端口 + 10000(固定 值)
- 节点与节点之间通过二进制协议进行通信
- 客户端和集群节点之间通信和通常一样,通过文本协议进行
- 集群节点不会代理查询
- 数据按照 Slot 存储分布在多个 Redis 实例上
- 集群节点挂掉会自动故障转移
- 可以相对平滑扩/缩容节点
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先 对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对 应一个编号 在 0~16383 之间的哈希槽, redis 会根据节点数量大致均等地将哈希槽映射到不同的节点
十八、Redis Cluster集群方案什么情况下会导致整个集群不可?
Redis 没有使用哈希一致性算法,而是使用哈希槽。 Redis 中的哈希槽一共有 16384 个,计算给 定 密钥的哈希槽,我们只需要对密钥的 CRC16 去取 16384。假设集群中有 A、B、C 三个集群 节点, 不存在复制模式下,每个集群的节点包含的哈希槽如下:
- 节点 A 包含从 0 到 5500 的哈希槽;
- 节点 B 包含从 5501 到 11000 的哈希槽;
- 节点 C 包含从 11001 到 16383 的哈希槽;
这时,如果节点 B 出现故障,整个集群就会出现缺少 5501 到 11000 的哈希槽范围而不可用。
十九、集群架构模式有哪几种?
Redis 集群架构是支持单节点单机模式的,也支持一主多从的主从结构,还支持带有哨兵的集群 部 署模式。
二十、Redis 常见性能问题和解决方案有哪些?
常见性能问题和解决方案如下:
- Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件;
- 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一 次;
- 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个 局域网内;
- 尽量避免在压力很大的主库上增加从库;
- 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…. ;这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂 了,可以立刻启用 Slave1 做 Master ,其他不变。
二十一、假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是 以某个固定的已知的前缀开头的,如果将它们全部找出来?
我们可以使用 keys 命令和 scan 命令,但是会发现使用 scan 更好。
1.直接使用 keys 命令查询,但是如果是在生产环境下使用会出现一个问题,keys 命令是遍历查询 的,查询的时间复杂度为 O(n),数据量越大查询时间越长。而且 Redis 是单线程,keys 指令会 导 致线程阻塞一段时间,会导致线上 Redis 停顿一段时间,直到 keys 执行完毕才能恢复。这 在生产 环境是不允许的。除此之外,需要注意的是,这个命令没有分页功能,会一次性查询出 所有符合条 件的 key 值,会发现查询结果非常大,输出的信息非常多。所以不推荐使用这个命令。
2.scan 命令可以实现和 keys 一样的匹配功能,但是 scan 命令在执行的过程不会阻塞线程,并且 查 找的数据可能存在重复,需要客户端操作去重。因为 scan 是通过游标方式查询的,所以不会导致 Redis 出现假死的问题。 Redis 查询过程中会把游标返回给客户端,单次返回空值且游 标不为 0,则 说明遍历还没结束,客户端继续遍历查询。 scan 在检索的过程中,被删除的元素是 不会被查询出来 ,但是如果在迭代过程中有元素被修改, scan 不能保证查询出对应元素。相对来说, scan 指令 查找花费的时间会比 keys 指令长。
二十二、如果有大量的 key 需要设置同一时间过期,一般需要注意什么吗?
如果有大量的 key 在同一时间过期,那么可能同一秒都从数据库获取数据,给数据库造成很大的 压力,导致数据库崩溃,系统出现 502 问题。也有可能同时失效,那一刻不用都访问数据库, 压力不够大的话,那么 Redis 会出现短暂的卡顿问题。所以为了预防这种问题的发生,最好给 数据的过期时间加一个随机值,让过期时间更加分散。
二十三、什么情况下可能会导致Redis阻塞?
Redis 产生阻塞的原因主要有内部和外部两个原因导致:
内部原因
- 如果 Redis 主机的 CPU 负载过高,也会导致系统崩溃;
- 数据持久化占用资源过多;
- 对 Redis 的 API 或指令使用不合理,导致 Redis 出现问题。
外部原因
- 外部原因主要是服务器的原因,例如服务器的 CPU 线程在切换过程中竞争过大,内存出现问题、 网 络问题等。
二十四、Redis 怎么提高缓存命中率?
- 提前加载数据到缓存中;
- 增加缓存的存储空间,提高缓存的数据;
- 调整缓存的存储数据类型;
- 提升缓存的更新频率。
二十五、Redis 如何解决 Key 冲突?
Redis 如果 key 相同,后一个 key 会覆盖前一个 key。如果要解决 key 冲突,最好给 key 取 好名区分开,可以按业务名和参数区分开取名,避免重复 key 导致的冲突
二十六、Redis 报内存不足怎么处理?
Redis 内存不足可以这样处理:
- 修改配置文件 redis.conf 的 maxmemory 参数,增加 Redis 可用内存;
- 设置缓存淘汰策略,提高内存的使用效率;
- 使用 Redis 集群模式,提高存储
二十七、缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
一、缓存雪崩
我们可以简单地理解为:由于原有缓存失效,新缓存未到其间 (例如:我们设置缓存时采用了相 同的 过期时间,在同一时刻出现大面积的缓存过期) ,所有原本应该访问缓存的请求都去查询数 据库了, 而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连 锁反应,造成 整个系统崩溃。 解决办法: 大多数系统设计者考虑用加锁( 最多的解决方案) 或者队列的方式保 证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的 并发请求落到底层存 储系统上。还有一个简单方案就是时间缓存失效时间分散开。
二、缓存穿透
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致 用 户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了 两次 无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。 解 决办法; 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不 存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。 另 外也有一个更为 简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系 统故障),我们仍然 把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通 过这个直接设置的默认值 存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据 库,这种办法最简单粗暴。
5TB 的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些 32bit 大小 的数 据该如何解决?如果是 64bit 的呢?
对于空间的利用到达了一种极致,那就是 Bitmap 和布隆过滤器(Bloom Filter)。 Bitmap :典型 的就 是哈希表 缺点是,Bitmap 对于每个元素只能记录 1bit 信息,如果还想完成额外的功能,恐 怕只能靠 牺牲更多的空间、时间来完成了。
布隆过滤器(推荐)
就是引入了 k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判 率 下,完成元素判重的过程。 它的优点是空间效率和查询时间都远远超过一般的算法,缺点是 有一定 的误识别率和删除困难。 Bloom-Filter 算法的核心思想就是利用多个不同的 Hash 函数来 解决“冲 突”。 Hash 存在一个冲突(碰撞)的问题,用同一个 Hash 得到的两个 URL 的值有可能相同。为 了减 少冲突,我们可以多引入几个 Hash,如果通过其中的一个 Hash 值我们得出某元素不在集 合中,那 么该元素肯定不在集合中。只有在所有的 Hash 函数告诉我们该元素在集合中时,才 能确定该元素存 在于集合中。这便是 Bloom-Filter 的基本思想。 Bloom-Filter 一般用于在大数 据量的集合中判定某 元素是否存在。
三、缓存预热
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易地理 解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户 请 求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路: 1、直接写个缓存刷新页面,上线时手工操作下; 2、数据量不大,可以在项目启动的 时候 自动进行加载; 3、定时刷新缓存;
四、缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis 默认的有 6 种策略可供选择), 我们 还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
- 定时去清理 过期的 缓存;
- 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话 就去底层系 统得到新数据并更新缓存。 两者各有优劣,第一种的缺点是维护大量缓存的 key 是比较麻烦的,第 二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!
具体用哪种方案,大家 可以根据自己的应用场景来权衡。
五、缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心 流 程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进 行自 动降级,也可以配置开关实现人工降级。 降级的最终目的是保证核心服务可用,即使是有 损的。而 且有些服务是无法降级的(如加入购物车、结算)。 以参考日志级别设置预案:
- 一般:比如 有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
- 警告: 有些服务在一 段时间内成功率有波动(如在 95%~100%之间),可以自动降级或人工降级,并发 送告警;
- 错误:比如可用率低于 90%,或者数据库连接池被打爆了,或者访问量突然猛 增到系统能承受的最大 阈值,此时可以根据情况自动降级或者人工降级;
- 严重错误:比 如因为特殊原因数据错误 ,此时需要紧急人工降级。
服务降级的目的,是为了防止 Redis 服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不 重 要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis 出现问题,不 去数据 库查询,而是直接返回默认值给用户。