什么是 Redis 缓存穿透?redis 面试题及答案乐分享(附面试题大全)
一、缓存雪崩

为什么使用缓存?
1.提高性能:缓存查询速度比数据库查询速度快(内存 vs 硬盘)。
2.提高并发能力:缓存分担了部分请求,支持更高的并发。
redis 存储的数据和内存占用是有限的,因此我们才需要对数据设置过期时间,并采用惰性删除+定期删除策略清除过期键,释放内存。如果数据缓存的过期时间是相同的,redis 正好把这部分数据清掉或者 redis 服务器出现故障,缓存失效请求全部走数据库,这种现象就是缓存雪崩。缓存雪崩可能导致数据库被搞垮,导致整个系统直接崩溃。
如何解决缓存雪崩?
数据预热,通过缓存 reload 机制,提前更新缓存,在发生大并发访问之前,手动触发加载缓存不同的 key,设置过期时间加上随机数值,让 key 过期时间分散开来。
redis 挂掉情况:使用 redis 高可用架构(主从架构+sentinel 或 redis cluster),保证个别节点或者机器宕掉依然能继续提供服务。
当缓存失效后,依赖隔离组件为后端限流降级(hystrix),采用加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
二、缓存穿透
当查询一个一定不存在的数据,由于缓存不命中,去查询数据库也无法查询出结果,因此不会写入到缓存中,这会导致每个查询都去请求数据库,造成缓存穿透。

解决方案:
布隆过滤对所有的可能查询的参数以 hash 形式存储,在控制器层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。bloomfilter 就类似于一个 hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个 key 是否存在于某容器,不存在就直接返回。举例:将真实正确 Id 在添加完成之后便加入到过滤器当中,每次再进行查询时,先确认要查询的 Id 是否在过滤器中,如果不在,则说明 Id 为非法 Id。
缓存空对象当从数据库查询不到值,就把参数和控制缓存起来,设置一个简短的过期时间(因为缓存是需要内存的,如果有过多空值 key,占用内存多),在该时间段如果有携带此参数再次请求,就可以直接返回。可能导致该段时间缓存层和数据库数据不一致,对于需要保持一致性的业务有影响。
三、缓存并发
高并发场景下同时查询大量查询过期的 key 值,最后查询数据库将缓存结果写到缓存、造成数据库压力过大。
解决方案:互斥锁 对锁不了解的可以查看初始锁的世界文章。
四、缓存和数据库双写一致性
数据库与缓存读写模式策略
写完数据库后是否需要马上更新缓存还是直接删除缓存?
如果数据库更新值和更新到缓存是一样的无需经过任何的计算,可以立即更新缓存,如果写多读少的场景也不适合这种方案。
如果数据保存缓存需要复杂的关联计算,无需立即更新缓存,等查询的时候在进行更新。
一般的策略是当更新数据时,先删除缓存,再更新数据库,而不是更新缓存,等要查询的时候才把最新的数据更新到缓存。
常见数据库与缓存双写情况导致数据不一致的场景和解决方案。
场景一:用户原来有 50 块,购买了一条毛巾 20,要更新余额为 30,数据库更新为 30,删除缓存但是失败了。意味着数据库是 30,而缓存还是 50,这导致数据库和缓存不一致解决方案:先删除缓存,如果删除成功再去更新数据库,就算数据库更新失败了,再次读取数据库缓存到 redis 数据还是一致的。
场景二:高并发的情况下,如果 A 删除完缓存,A 在去更新数据库,在更新的过程后,B 来查询数据,发现缓存没有,就从数据库获取并缓存,后面 A 更新成功,就会出现缓存和数据库不一致。解决方案:使用队列,购票场景,更新车票库存,先把票 id 丢到队列里去,当更新完后在从队列里去除,在更新票数的过程中,如果有查票库存请求先去缓存里看下有没有数据,如果没有,可以先去队列里看是否有用户在做更新,如果有也把查票库存请求发送到队列里去,然后同步等待缓存更新完成。如果发现队列里有一个查询请求了,那么就不要放新的查询操作进去了,用一个 while(true)循环去查询缓存,循环个 200MS 左右,如果缓存里还没有则直接取数据库的旧数据。高并发解决注意点: 1.读请求时间堵塞 读请求进行非常轻度的异步化,每个读请求必须在超时间内返回,该方案最大的风险在于可能数据更新很频繁,导致队列中挤压了大量的更新操作在里面,然后读请求会发生大量的超时,导致大量的请求直接走数据库,像遇到这种情况,一般要做好足够的压力测试,如果压力过大,需要根据实际情况添加机器。 2.请求并发量过高 压力测试要做好,模拟各种真实场景,并发量在最高的时候 QPS 多少,如何扛不住可以多加机器,同时读写服务器比例要设置好。 3. 多服务实例部署的请求路由 服务部署了多个实例,要保证执行数据更新操作和执行缓存更新操作的请求都通过 nginx 服务器路由到相同的服务器实例上。 4.热点数据的路由问题,导致请求 读热点数据的请求非常高,全部发给同一机器的同一队列里,造成该服务器压力过大,因为只能更新数据才会清空缓存,才会导致读写并发。
保证数据一致的方案:
1. databus
Databus 是一个低延迟、可靠的、支持事务的、保持一致性的数据变更抓取系统。由 LinkedIn 于 2013 年开源。Databus 通过挖掘数据库日志的方式,将数据库变更实时、可靠的从数据库拉取出来,业务可以通过定制化 client 实时获取变更并进行其他业务逻辑。搭建过程简述:下载 ojdbc6.jar 然后复制放在 sandbox-repo/com/oracle/ojdbc6/11.2.0.2.0/,然后重命名 ojdbc6-11.2.0.2.0.jar,构建需要 gradle 支持。

2. 阿里的 canal 监听 binglog 进行更新。
canal 是阿里巴巴旗下的一款开源项目,纯 Java 开发。基于数据库增量日志解析,提供增量数据订阅 &消费,目前主要支持了 MySQL(也支持 mariaDB)。
如何使用 redis 做一个消息队列?

redis 知识查缺补漏---面试题
一个字符串类型的值能存储最大容量是 521M。
为什么 redis 需要把所有数据放在内存中?为了达到最大的读写速度将数据都读到内存中,并通过异步方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度会严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
Redis 支持的数据类型?String 字符串:格式: set key valuestring 类型是二进制安全的。redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象 。常规 key-value 缓存应用;常规计数:微博数,粉丝数等。string 类型是 Redis 最基本的数据类型,一个键最大能存储 512MB。 Hash(哈希)格式: hmset name key1 value1 key2 value2Redis hash 是一个键值(key=>value)对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。 List(列表)Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)格式: lpush name value 在 key 对应 list 的头部添加字符串元素格式: rpush name value 在 key 对应 list 的尾部添加字符串元素格式: lrem count value 在 key 对应 list 中删除 count 个和 value 相同的元素格式: llen name 返回 key 对应 list 的长度使用场景:微博的关注列表,粉丝列表,消息列表等功能都可以用 Redis 的 list 结构来实现。 Set(集合)格式: sadd name valueRedis 的 Set 是 string 类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。 zset(sorted set:有序集合)格式: zadd name score valueRedis zset 和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。场景:在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。
Redis 集群方案什么情况下会导致整个集群不可用?有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。
Redis 支持的 Java 客户端都有哪些?官方推荐用哪个?Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson。
redis 和 redisson 有什么关系?Redisson 是一个高级的分布式协调 Redis 客服端,能帮助用户在分布式环境中轻松实现一些 Java 的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。
Jedis 与 Redisson 对比有什么优缺点?Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持;Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等 Redis 特性。Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让开发者能够将精力更集中地放在处理业务逻辑上。
redis 有哪些架构?各自的特点?单机版

特点:简单
缺点: 内存容量有限 处理能力有限 无法高可用
主从复制

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
特点:master/slave 角色 master/slave 数据相同 降低 master 读压力转交 salve 分担。
缺点:无法保证高可用 没有解决 master 写的压力
哨兵模式:

Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:
监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。
特点:保证高可用 监控各个节点 自动故障迁移
缺点:主从模式切换需要时间可能会丢数据 没有解决 master 写的压力。
集群(proxy)型

Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器;Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。
特点:多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins 支持失败节点自动删除 后端 sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 redis 一致。
缺点:增加了新的 proxy,需要维护其高可用 failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预。
集群直连型:

从 redis 3.0 之后版本支持 redis-cluster 集群,Redis-Cluster 采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
特点: 1.无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
2.数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布
3.可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
4.高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
5.实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升。
缺点:资源隔离性较差,容易出现相互影响的情况和数据通过异步复制,不保证数据的强一致性。
9.redis 集群如何选择数据库?
redis 集群目前无法做数据库选择,默认在 0 数据库。
10. redis 集群之间如何复制的?最大节点个数是多少?
异步复制 16384 个
11. redis 内存如何做优化?
能用散列表存储就优先选择,占用的内存小。如果要保持用户的基本信息,直接用散列保存,不要为每个属性单独设 key。
12.redis 回收使用的是什么算法?
LRU 算法
13.redis 回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据。
Redis 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收。
一个新的命令被执行,等等。
所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
14.redis 如何做大量数据插入?
redis2.6 开始 redis-cli 支持 pipe model 模式,提供大量数据的插入。
15.redis 为什么要分区?
分区可以让 redis 管理更大的内存,redis 将可以使用所有机器的内存。如果没有分区,最多只能使用一台机器的内存。分区使 redis 的计算能力通过简单地增加计算机得到成倍提升,redis 的网络带宽也会随着计算机和网卡的增加而成倍增长。
16.redis 分区方案有哪些?
客户端分区:在客户端已经决定数据会被存储到哪个 redis 节点或者从哪个 redis 节点读取。大多数客户端已经实现了客户端分区。
代理分区:客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些 redis 实例,然后根据 redis 的响应结果返回客户端。redis 和 memcache 的一种代理实现就是 twemproxy。
查询路由(query routing):客户端随机地请求任意一个 redis 实例,然后由 redis 将请求转发给正确的 redis 节点。redis cluster 实现了一个混合形式的查询路由,并不是直接将请求从一个 redis 节点转发到另一个 redis 节点,而是在客户端的帮助下直接 redirected 到正确的节点。
17.redis 分区有什么缺点?
涉及多个 key 的操作通常不会被支持。如不能直接使用交集指令对俩个集合求交集,因为集合存储的 redis 实例可能不同。
操作多个 key 不能使用 redis 事务。
分区使用的粒度是 key,不能使用一个非常长的排序 key 存储一个数据集。(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set.)
当使用分区的时候,数据处理会非常复杂。例如为了备份必须从不同的 redis 实例和主机同时收集 rdb/apf 文件。
分区时动态扩容或缩容可能非常复杂。redis 集群在运行时增加或者删除 redis 节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区则不支持这种特性,可以使用预分片技术来解决。
18.redis 持久化数据和缓存如何做扩容?
如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。
如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。
19.分布式 redis 是前期做还是后期规模上来再做?为什么?
Redis 是如此的轻量(单实例只使用 1M 内存),为防止以后的扩容,最好就是一开始就启动较多实例。即便你只有一台服务器,一开始就让 Redis 以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。
一开始就多设置几个 Redis 实例,例如 32 或者 64 个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。
当你的数据不断增长,需要更多的 Redis 服务器时,你需要做的就是仅仅将 Redis 实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的 Redis 实例从第一台机器迁移到第二台机器。
20.Twemproxy 是什么?
Twemproxy 是 Twitter 维护的(缓存)代理系统,代理 Memcached 的 ASCII 协议和 Redis 协议。它是单线程程序,使用 c 语言编写,运行起来非常快。它是采用 Apache 2.0 license 的开源软件。Twemproxy 支持自动分区,如果其代理的其中一个 Redis 节点不可用时,会自动将该节点排除(这将改变原来的 keys-instances 的映射关系,所以你应该仅在把 Redis 当缓存时使用 Twemproxy)。Twemproxy 本身不存在单点问题,因为你可以启动多个 Twemproxy 实例,然后让你的客户端去连接任意一个 Twemproxy 实例。Twemproxy 是 Redis 客户端和服务器端的一个中间层,由它来处理分区功能应该不算复杂,并且应该算比较可靠的。
21.支持一致性哈希的客户端有哪些?
redis-rb predis
22.redis 是单线程的,如何提高多核 cpu 的利用率?
可以在同一个服务器部署多个 redis 的实例,当成不同的服务器来使用。在某些时候,无论如何一个服务器是不够的,所以你想使用多个 cpu,可以考虑分片(shard)。
23.一个 redis 实例最多能存放多少的 keys?list、set、sorted set 最多能存放多少元素?
一个 redis 至少能存 2 亿 5 千万的 keys。任何 list、set、sorted set 都可以存放 232 个元素。redis 的存储极限主要还是取决于系统的可用内存值。
24.redis 常见性能问题和解决方案?
(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次。
(3) 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内。
(4) 尽量避免在压力很大的主库上增加从库。
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。
25.redis 如何实现延迟任务?
我们知道 zset 本质就是 set 结构上加了排序功能,当添加或者修改元素,会按照 score 值进行排序。如果 score 代表是执行时间的时间戳,某个时间加入到 zset 集合中就会排序,然后写一个死循环不断取第一个 key 值,如果当前时间戳大于 key 的时间戳就取出来消费删除,这样就可以执行延迟任务的目的,而且不需要遍历整个 zset,造成性能浪费。

本文主要介绍了 redis 一些比较经典的面试题,通过这些题目有助于大家更好的理解 redis。如果觉得还不错的小伙伴请关注我,后面持续会推出好文。

努力不一定有结果,但是不努力一定不会有结果。
需要的小伙伴可以转发此文关注小编,查看下方图片来获取!

评论