Java面试常问问题——Redis篇
【本文章旨在帮助你快速了解Java面试中会遇到的高频面试题大部分内容来自黑马程序员的Java面试课程-视频链接以及两个很好用的面试题网站小林coding-网站链接 和JavaGuide-网站链接我将这三者进行了总结和归纳希望能帮助到你也希望你在准备面试题的时候不只是“背会了”而是“通过准备面试题拓展自己的知识面掌握技术背后的细节和逻辑”。准备面试是一个煎熬焦虑迷茫的过程多看天多想想多走走。】最高优先级必问出现频率极高Redis缓存相关问题穿透/击穿/雪崩- 80%的面试必问Redis和MySQL数据同步问题- 实际项目经验重点Redis分布式锁实现- 并发场景必考Redis持久化机制AOF/RDB- 基础原理必问高优先级经常被问到Redis数据结构及应用场景- 基础中的基础Redis数据淘汰策略- 内存管理必问Redis集群方案主从复制、哨兵模式- 高可用方案Redis为什么这么快单线程优势- 原理性问题中优先级偶尔问到Redis分片集群数据分片、哈希槽- 大数据量场景Redis数据过期策略- 基础原理Redis集群脑裂问题- 高级问题布隆过滤器原理- 缓存穿透解决方案细节低优先级选择性准备Redisson实现细节可重入锁、看门狗- 框架细节RedLock红锁问题- 分布式锁进阶AOF刷盘策略比较- 持久化细节RDB写时复制技术- 原理细节可以简化的Redis集群脑裂知道概念即可不必深入细节RedLock红锁知道存在问题不用记具体实现AOF/RDB详细机制知道是什么、区别、优缺点即可最重要的5个问题时间紧张就看这些缓存穿透、击穿、雪崩的区别和解决方案Redis和MySQL如何保证数据一致性Redis分布式锁怎么实现有什么问题Redis持久化有哪两种方式有什么区别Redis的数据结构有哪些分别用在什么场景RedisRedis数据结构以及应用场景 高频首先我们要知道此处的数据类型指的是value的类型Redis的key都是字符串类型常见的String字符串可以用来存储任何类型的数据比如字符串、整数、浮点数、图片图片的 base64 编码或者解码或者图片的路径、序列化后的对象。应用场景任何常规数据分布式锁setnx key value主要命令SETGETMSET批量MGETHash散列值是 field-value 结构field为属性value为字段的值。应用场景非常适合存储对象。主要命令HSET HGETHMSETHMGETString和Hash谁更适合存储对象String存储对象是先把对象转为 Json 格式需要使用再反序列化回来适合对象整体结构简单且读 写整个对象是主要操作时Hash是对对象的每个字段单独存储可以获取部分字段的信息也可以修改或者添加部分字段如果对象中的字段信息经常变动或者我们经常要获取对象某个字段Hash 就非常适合。问购物车信息用 String 还是 Hash 存储更好呢?由于购物车中的商品频繁修改和变动购物车信息建议使用 Hash 存储用户 id 为 key商品 id 为 field商品数量为 valueList列表与Java中的LinkedList类似可以看做是一个双向链表结构。有序元素可以重复插入删除快查询一般应用场景适合存储一个有序数据例如朋友圈点赞列表评论列表等。主要命令LPUSHLPOP–在左边插入删除元素 RPUSHRPOP–在右边插入删除元素BLPOPBRPOP–阻塞一段时间再移除元素模拟栈入口和出口在同一边模拟队列入口和出口不在同一边模拟阻塞队列入口和出口不在同一边且出队列采用 BLPOP 或 BRPOPSet集合与 Java 中的 HashSet类似无序元素不重复查找快支持交集、并集、差集应用场景存放数据不能重复的场景网站 UV (访问人数统计数据量小用户的共同好友。ZSet可排序集合每一个元素都带有一个score属性可以基于score属性排序注意score值越大排名越靠后排名从0开始应用场景适合用来做排行榜。不常见的BitMap位图二进制数组存放0和1适合统计活跃用户用户一段时间内的登录次数HyperLogLog基数统计适合统计网站UV网站人数多Geo (地理位置)适合存储地理信息Stream流适合做消息队列Redis缓存相关问题1.缓存穿透 必问缓存空值 布隆过滤器误判缓存穿透是指查询一个一定不存在的数据由于存储层查不到数据因此不写入缓存这将导致这个不存在的数据每次请求都要到 DB 去查询可能导致 DB 挂掉。这种情况大概率是遭到了攻击。解决方案的话我们通常都会用布隆过滤器来解决它。布隆过滤器布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是Redisson实现的布隆过滤器。它的底层原理是先初始化一个比较大的数组里面存放的是二进制0或1。一开始都是0当一个key来了之后经过3次hash计算模数组长度找到数据的下标然后把数组中原来的0改为1。这样三个数组的位置就能标明一个key的存在。查找的过程也是一样的。当然布隆过滤器有可能会产生一定的误判我们一般可以设置这个误判率大概不会超过5%。其实这个误判是必然存在的要不就得增加数组的长度。5%以内的误判率一般的项目也能接受不至于高并发下压倒数据库。2.缓存击穿必问setnx互斥锁 逻辑过期时间缓存击穿的意思是对于设置了过期时间的key缓存在某个时间点过期的时候恰好这个时间点对这个Key有大量的并发请求过来。这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存这个时候大并发的请求可能会瞬间把 DB 压垮。解决方案有两种方式第一可以使用互斥锁当缓存失效时不立即去load db先使用如 Redis 的SETNX去设置一个互斥锁。当操作成功返回时再进行 load db的操作并回设缓存否则重试get缓存的方法。第二种方案是设置当前key逻辑过期大概思路如下1) 在设置key的时候设置一个过期时间字段一块存入缓存中不给当前key设置过期时间2) 当查询的时候从redis取出数据后判断时间是否过期3) 如果过期则开通另外一个线程进行数据同步当前线程正常返回数据这个数据可能不是最新的。当然两种方案各有利弊如果选择数据的强一致性建议使用分布式锁的方案但性能上可能没那么高且有可能产生死锁的问题。如果选择key的逻辑删除则优先考虑高可用性性能比较高但数据同步这块做不到强一致。3.缓存雪崩必问缓存过期时间设随机值缓存雪崩意思是设置缓存时采用了相同的过期时间导致缓存在某一时刻同时失效请求全部转发到DBDB瞬时压力过重而雪崩。与缓存击穿的区别是雪崩是很多key而击穿是某一个key缓存。解决方案主要是可以将缓存失效时间分散开。比如可以在原有的失效时间基础上增加一个随机值比如1-5分钟随机。这样每一个缓存的过期时间的重复率就会降低就很难引发集体失效的事件。4.Redis和Mysql数据同步问题必问Qredis作为缓存mysql的数据如何与redis进行同步呢在我“优觅生活”的项目中我采用的方案是使用采用Cache-Aside 模式作为缓存基础架构。什么是Cache-Aside 模式呢如果是读操作先尝试从缓存读取数据。如果缓存命中直接返回数据。如果缓存未命中从数据库查询数据将查到的数据放入缓存并返回数据。如果是写操作,先更新数据库。再直接删除缓存中对应的数据。同时呢我针对不同业务场景组合运用Redission读写锁阿里巴巴Canal组件解决数据一致性问题。A我通过两个地方向您表述1、嗯就说我做的项目吧里面有缓存店铺分类信息(分类名称使用的图片地址创建时间修改时间等。数据同步可以有一定的延时这符合大部分业务需求。我们当时采用的阿里的Canal组件实现数据同步不需要更改业务代码只需部署一个Canal服务。Canal服务把自己伪装成mysql的一个从节点。当mysql数据更新以后Canal会读取binlog日志中的数据然后再通过Canal的客户端获取到数据并更新缓存即可。有了canal还需要cache-Aside模式吗需要因为当第一个用户到来他去缓存中拿数据但是此时mysql数据库中没有数据变更canal无法发挥作用还得是基础的 mysql读数据—写到缓存中2、还有一个地方在我的平台中商家可以编辑自己的店铺信息店铺名称营业时间公告等这是一个对数据一致性要求比较高的场景假如商家临时更改了营业时间用户读到旧数据会白跑一趟我们使用的是Redisson实现的读写锁。在读的时候添加共享锁可以保证读读不互斥、读写互斥。当我们更新数据的时候添加排他锁。它是读写、读读都互斥这样就能保证在写数据的同时是不会让其他线程读数据的避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。Q那这个排他锁是如何保证读写、读读互斥的呢A其实排他锁底层使用的也是SETNX它保证了同时只能有一个线程操作锁住的方法。Q:你听说过延时双删吗为什么不用它呢A延迟双删如果是写操作我们先把缓存中的数据删除然后更新数据库最后再延时删除缓存中的数据。延时是因为我们的数据库一般是主从模式读写分离的。我们需要延时一会把主节点的数据同步到从节点。其中这个延时多久不太好确定。在延时的过程中可能会出现脏数据所以没有采用它。5.Redis数据持久化 必问Q: redis作为缓存数据的持久化是怎么做的A: 在Redis中提供了两种数据持久化的方式1.AOF日志 2.RDB快照默认AOF的实现原理Redis 在执行命令后会将对应的命令追加写入到 AOF 文件的缓冲区然后根据配置的策略定期将缓冲区内容刷写到磁盘上的 AOF 文件中。 当Redis 重启时会读取AOF文件的命令通过逐一执行命令的方式来进行数据恢复。AOF刷盘策略Redis 提供了 3 种写回硬盘的策略Always这个词的意思是「总是」所以它的意思是每次写操作命令执行完后同步将 AOF 日志数据写回硬盘Everysec这个词的意思是「每秒」所以它的意思是每次写操作命令执行完后先将命令写入到 AOF 文件的缓冲区中然后每隔一秒将缓冲区里的内容写回硬盘No意味着将刷盘策略转交给操作系统控制也就是每次写操作命令执行完后先将命令写入到 AOF 文件的缓冲区中再由操作系统决定于何时将缓冲区内容写回硬盘。Redis 为了避免 AOF 文件越写越大提供了 AOF 重写机制当 AOF 文件的大小超过所设定的阈值后Redis 就会启用 AOF 重写机制来压缩 AOF 文件。 set name A set name B — set name B(A被覆盖)RDB的实现原理RDB是一个快照文件。它是把redis内存存储的数据写到磁盘上。当redis恢复数据的时候可以从RDB的快照文件中恢复数据。RDB 做快照时会阻塞线程吗Redis 提供了两个命令来生成 RDB 文件分别是 save 和 bgsave默认它们的区别就在于是否在「主线程序」里执行执行了 save 命令就会在主线程序生成 RDB 文件如果写入 RDB 文件的时间太长会阻塞主线程序执行了 bgsave 命令会创建一个子进程来生成 RDB 文件这可以避免主线程序的阻塞。RDB 在执行快照的时候数据能修改吗可以的执行 bgsave 过程时数据是能被修改的关键的技术就在于写时复制技术Copy-On-Write, COW。执行 bgsave 命令的时候会通过 fork() 创建子进程子进程会复制父进程的页表(记录虚拟地址和物理地址的映射关系什么是页表呢我们的Redis一般都是部署在linux操作系统中的而在linux里应用程序无法直接操作物理内存地址但是可以通过操作虚拟地址来间接操作物理地址而页表就记录着虚拟地址和物理地址的映射关系子进程复制完父进程的页表后此时子进程和父进程是共享同一片内存数据的。如果主线程序执行写操作则被修改的数据会复制一份副本然后子进程会把该副本数据写入 RDB 文件在这个过程主线程仍然可以直接修改原来的数据。AOF和RDB的优缺点RDB因为比较轻量它在数据恢复的时候是很快的但是如果redis在两次快照之间宕机了RDB就会丢失这段时间内的数据。并且 bgsave 命令会消耗很多 cpu 资源AOF在恢复数据的时候比较慢但是它在配置 everysec 命令时如果redis宕机了最多会丢失一秒的数据它的数据安全性比较高。我们在项目中是采用 RDB 和 AOF 混合持久化的。6.Redis数据过期策略 高频在redis中提供了两种数据过期删除策略。第一种是惰性删除。在设置该key过期时间后我们不去管它。当需要该key时我们检查其是否过期。如果过期我们就删掉它反之返回该key。第二种是定期删除。就是说每隔一段时间我们就对一些key进行检查并删除里面过期的key。Redis的过期删除策略是惰性删除 定期删除两种策略配合使用。7.Redis的数据淘汰策略 高频Redis 数据淘汰策略共有八种这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略。1、不进行数据淘汰的策略• noevictionRedis3.0之后默认的内存淘汰策略如果说我们 redis 的占用内存超过最大设置内存时不淘汰任何数据这时如果有新的数据写入会报错不淘汰任何数据。2、进行数据淘汰的策略针对「进行数据淘汰」这一类策略又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略。• 在设置了过期时间的数据中进行淘汰• volatile-random随机淘汰设置了过期时间的任意键值• volatile-ttl优先淘汰较早过期的键值。• volatile-lruRedis3.0 之前默认的内存淘汰策略淘汰所有设置了过期时间的键值中最久未使用的键值• volatile-lfuRedis 4.0 之后新增的内存淘汰策略淘汰所有设置了过期时间的键值中最少使用的键值• 在所有数据范围内进行淘汰• allkeys-random随机淘汰任意键值• allkeys-lru淘汰整个键值中最久未使用的键值• allkeys-lfuRedis 4.0 之后新增的内存淘汰策略淘汰整个键值中最少使用的键值。数据库有1000万数据Redis只能缓存20w数据。如何保证Redis中的数据都是热点数据**候选人嗯我想一下()。可以使用allkeys-lru挑选最近最少使用的数据淘汰淘汰策略。那留下来的都是经常访问的热点数据。Redis的内存用完了会发生什么候选人嗯~这个要看redis的数据淘汰策略是什么。如果是默认的配置redis内存用完以后则直接报错。我们当时设置的是allkeys-lru策略把最近最常访问的数据留在缓存中。8.Redis分布式锁怎么实现 高频在redis中提供了一个命令SETNX(SET if not exists)。由于redis是单线程的用了这个命令之后只能有一个客户端请求能对某一个key设置值。在没有过期或删除key的时候其他客户端是不能设置这个key的。那你如何控制Redis实现分布式锁的有效时长呢候选人嗯的确。redis的SETNX指令不好控制这个问题。我们当时采用的是redis的一个框架Redisson实现的。在Redisson中需要手动加锁并且可以控制锁的失效时间和等待时间。当锁住的一个业务还没有执行完成的时候Redisson会引入一个看门狗机制。就是说每隔一段时间就检查当前业务是否还持有锁。如果持有就增加加锁的持有时间。当业务执行完成之后需要使用释放锁就可以了。还有一个好处就是在高并发下一个业务有可能会执行很快。客户1持有锁的时候客户2来了以后并不会马上被拒绝。它会自旋不断尝试获取锁。如果客户1释放之后客户2就可以马上持有锁性能也得到了提升。Redisson实现的分布式锁是可重入的吗候选人嗯是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁如果是当前线程持有的锁就会计数如果释放锁就会在计数上减一。在存储数据的时候采用的hash结构大key可以按照自己的业务进行定制其中小key是当前线程的唯一标识value是当前线程重入的次数计数器。Redisson实现的分布式锁能解决锁的主从一致性的问题吗候选人这个是不能的。我们Redis一般都是部署的主从哨兵模式主节点用来写数据从节点用来读数据。如果线程1通过setnx获取到锁之后主节点的数据还没有同步到从节点就宕机了哨兵会将一个从节点提升为新的主节点而老的主节点恢复之后会被降为从节点并做主节点数据的全量同步。此时来了一个线程2通过新的主节点也可以获取到锁这样就导致两个线程产生“共享同一把锁”的情况线程1仍然认为自己持有锁违背了锁的互斥性。我们可以利用Redisson提供的红锁来解决这个问题它的主要作用是不能只在一个Redis实例上创建锁而是要求在大多数Redis节点上都成功创建锁红锁中要求是成功创建锁的Redis节点数量要过半。这样就能避免线程1加锁成功后主节点宕机导致线程2成功加锁到新的主节点上的问题了。但是如果使用了红锁性能会变差。我们使用Redis本身就是追求高可用的我们可以想办法做到最终一致性Redis也不会经常性的宕机如果有强一致性要求高的业务建议使用ZooKeeper实现的分布式锁它是可以保证强一致性的。9.Redis集群有哪些知道吗 中频在Redis中提供的集群方案总共有三种主从复制、哨兵模式、Redis分片集群。那你介绍一下主从同步单节点Redis的并发能力是有上限的要进一步提高Redis的并发能力可以搭建主从集群实现读写分离。一般都是一主多从主节点负责写数据从节点负责读数据主节点写入数据之后需要把数据同步到从节点中。主从同步分为了两个阶段一个是全量同步一个是增量同步。全量同步是指从节点第一次与主节点建立连接的时候使用全量同步流程是这样的第一从节点请求主节点同步数据其中从节点会携带自己的replication id和offset偏移量。第二主节点判断是否是第一次同步主要判断的依据就是主节点与从节点是否是同一个replication id如果不是就说明是第一次同步那主节点就会把自己的replication id和offset发送给从节点让从节点与主节点的信息保持一致。第三在同时主节点会执行BGSAVE生成RDB文件后发送给从节点去执行从节点先把自己的数据清空然后执行主节点发送过来的RDB文件这样就保持了一致。当然如果在RDB生成期间依然有请求到了主节点而主节点会以命令的方式记录到缓冲区日志文件中最后把这个日志文件发送给从节点这样就能保证主节点与从节点完全一致了后期再同步数据的时候都是依赖于这个日志文件这个就是全量同步。增量同步指的是当从节点服务重启之后数据就不一致了所以这个时候从节点会请求主节点同步数据主节点还是判断不是第一次请求不是第一次就获取从节点的offset值然后主节点从命令日志中获取offset值之后的数据发送给从节点进行数据同步。如何保证Redis的高并发高可用首先就是可以搭建主从集群再加上使用Redis中的哨兵模式。我来介绍一下哨兵模式什么是哨兵模式有什么作用在 Redis 中由于主从模式是读写分离的如果主节点挂了那么将没有主节点来服务客户端的写操作请求也没有主节点给从节点同步数据。Redis 提供了哨兵机制哨兵节点主要负责三件事情监控、选主、通知。监测主节点是否存活如果发现主节点挂了它就会选择一个从节点升级为主节点并把新主节点的相关信息通知给所有从节点和客户端。如何判断主节点真的故障了哨兵会每隔1秒给所有节点发送PING命令当主从节点收到PING命令后会发送一个响应命令PONG给哨兵。如果主节点或者从节点没有在规定的时间内响应哨兵的PING命令哨兵就会将它们标记为【主观下线】。为了降低误判率当一个哨兵判断主节点为「主观下线」后就会向其他哨兵发起命令其他哨兵收到这个命令后会做出赞成投票或者否决投票的响应。如果票数大于默认配置的 N/2 1(N为哨兵节点的数量)这时主节点就会被标记为【客观下线】。选主节点的规则我们首先要把已经下线的从节点过滤掉再把网络状态不好的从节点给过滤掉接下来要对所有从节点进行三轮考察优先级、复制进度、ID号。第一轮考察哨兵会根据从节点的优先级来进行排序优先级越小排名越前。优先级在配置中设置第二轮考察如果优先级相同则查看复制进度哪个从节点复制的数据多哪个就靠前。比较从节点和主节点的offset第三轮考察如果优先级和复制进度都相同就选择从节点ID较小的那个。Redis集群脑裂该怎么解决呢脑裂的问题是这样的我们现在用的是Redis的哨兵模式。如果主节点的网络突然发生了问题它与所有的从节点失联了使得哨兵不能心跳感知到主节点所以通过选举的方式提升了一个从节点为新的主节点这样就存在了两个主节点就像大脑分裂了一样。但是此时老的主节点和客户端的网络是正常的这样会导致客户端还在出问题的老主节点那里写入数据新节点无法同步数据当网络恢复后哨兵会将老主节点降为从节点这时再从新主节点同步数据因为第一次同步是全量同步此时的从节点会清空掉自己本地的数据然后再做全量同步。这会导致老主节点在出问题时期中的数据丢失。关于解决的话在Redis的配置中可以设置第一可以设置最少的从节点个数比如设置至少要有一个从节点才能接收数据第二个可以设置主从数据同步的延迟时间不能超过5秒达不到要求原主节点就会被限制从客户端写入数据。Redis分片集群有什么用分片集群主要解决的是海量数据的存储。集群中有多个master每个master存储不同的数据还可以给每个master设置多个slave。大大提高了并发能力和存储数据能力。另一方面多个master之间通过ping检测彼此的健康状态。类似哨兵模式。当客户请求访问时可以访问集群中的任意节点请求最终都会转发到正确的节点上。redis分片集群中数据怎么存储和读取redis集群引入hash槽概念有16384个hash槽每个master节点绑定一定范围的hash槽。key通过CRC16算法计算得到一个值再模于16384得到值决定放置在哪个槽。通过槽找到对应的节点进行存储。取值的逻辑也是一样的。10.Redis是单线程的为什么那么快? 中频1.Redis 的大部分操作都在内存中完成因此 Redis 瓶颈可能是机器的内存或者网络带宽而并非 CPU既然 CPU 不是瓶颈那么自然就采用单线程的解决方案2.Redis 采用单线程模型可以避免多线程之间的竞争省去了多线程切换带来的时间和性能上的开销。3.Redis 采用了 I/O 多路复用技术来处理大量客户端 的 Socket 请求一个线程处理多个I/O流。这个机制让内核可以同时监听多个 Socket内核会持续监控这些 Socket 上是否有新的连接请求。一旦有请求发生内核会通知 Redis之后由 Redis 线程对这些请求进行处理从而极大提升了性能和效率。