java面试八股 redis篇

1.redis使用场景

缓存:    缓存三兄弟 穿透 击穿 雪崩 双协一致 持久化 数据过期策略 数据过期策略

分布式锁:   setnx redisson

消息队列 延迟队列  何种数据类型

 

2.缓存击穿

缓存穿透:通过查询一个不存在的数据,数据库查询不到数据也不会写入缓存,导致每次都直接查数据库

 

解决方法一 :缓存空数据,查询返回的数据为空,仍贾昂这个空结果进行缓存

优点:简单

缺点:消耗内存,可能发送不一致问题(一开始 id为 10000 可能数据库中没有,然后缓存了空。后面数据库添加了这个数据 就导致缓存和数据库不一致)

解决方法二:布隆过滤器

根据id查询数据   查询布隆过滤器         布隆过滤器        预热缓存时,预热布隆过滤器      redis查询不到查询数据库

                            不存在 直接返回                                  布隆过滤器存在,返回redis

bitmap(位图)二进制数组   

布隆过滤器 :检索一个元素是否在一个集合追踪

存储数据,通过多个hash函数,获取hash值,将其hash数改为1

优点:内存占用较小,没有多余的key

缺点:实现复杂,存在误判 要求95%以上正确率

 

3.缓存穿透

 

缓存击穿:某一个key设置了过期时间,当key过期的时候,恰好这个时间点对这个key有大量的并发请求过来,这些并发请求可能压倒数据库

解决方法一:互斥锁

线程一: 1查询缓存未命中 2获取互斥锁成功 3查询数据库重建缓存数据

线程二: 1查询缓存未命中  2获取互斥锁失败   3重试 4重试  5缓存命中

优点:强一致

缺点:性能差

 

解决方法二:逻辑过期 (不设置过期时间)

在数据库中添加一个字段  过期时间

线程一: 1查询缓存 发现逻辑过期 已过期  2获取互斥锁成功 3开始一个新的线程去 重构缓存 4返回过期数据

线程二: 1 查询数据库重建缓存数据  2写入缓存 重置逻辑过期时间 3 释放锁

进程三: 1查询缓存 发现逻辑过期 已过期 2获取互斥锁成功 3返回过期数据

优点:高可用 性能优

缺点:无法做到强一致

 

4.缓存雪崩

缓存雪崩:同一时间大量的缓存key同时失效或者redis服务宕机,导致大量请求来到数据库,带来巨大压力

解决方案一:给不同的key的ttl添加随机值

解决方案二:利用redis集群提高服务的可用性  例如 哨兵模式 集群模式

解决方案三:给缓存业务增加 降级限流的策略 在springboot中 使用ngxin  springcloud中 使用gateway网关进行配置

解决方案四:给业务添加多级缓存  Guava或者Caffeine 一级缓存 redis 二级缓存

 

5.双写一致性

双写一致性:当修改了数据库的数据,也要同时更新缓存的数据,缓存和数据库保持一致

1.一致性要求高

互斥锁。存入缓存的数据 一般都是 读多写少

所以可以使用 redisson读写锁

共享锁:加锁后,其他线程可以读操作,但是不可以写

排他锁:加锁就,阻塞其他线程读写操作

 

2.允许短暂不一致(大多数情况)

异步通知 保证数据的最终一致性

1.使用MQ

修改数据  写入数据库 发一条消息给MQ  缓存去监听MQ  去更新缓存

(需要保证MQ的可靠性)

 

2.使用Canal (阿里的中间件)MySQL 数据库增量日志解析工具      代码低侵入 基本不需要修改代码

基于mysql的主从同步来实现的。 

修改数据  写入数据库 (数据库会自动记录到 binlog文件 所有的 dll 数据定义语句 和 dml 数据库操作语句 不包括查询)

canal会监听 mysql的binlog 去更新缓存 

 

6.持久化

对数据安全要求高,将两者进行结合。

1.RDB

数据快照,将内存中的数据全部记录到磁盘中,当redis实例故障重启后,从磁盘读取快照文件,恢复数据。

save      由redis主进程来执行RDB,,会阻塞所有命令

bgsave  开启子进程执行RDB,避免主进程收到影响

redis 内部有触发RDB的机制  在redis.conf文件中 

900秒内,如果只要有一个key被修改,则执行bgsave

save 900 1

save 300 10

save 60 10000

执行原理: bgsave开始时会fork主进程得到子进程,子进程 共享 主进程的内存数据。完成fork后读取内存数据并写入RDB文件。

fork使用的是 copy-on-write技术 

主进程有一个 页表(这个页表会记录 虚拟地址和物理地址的映射关系 ) 子进程会 拷贝 这个页表 有相同的映射关系,实现内存共享。

主进程在写数据  子进场在读写数据,会导致脏数据出现 (写新的RDB文件 覆盖就得RDB文件)

 

2.AOF

追加文件,redis处理的每一个写命令都会记录在AOF文件中,可以看做命令日志文件

默认是关闭状态 修改redis.conf配置文件来开启AOF  appendonly

每执行一次写命令,立即记录到AOF文件

写完命令执行完后先放入AOF缓冲区,每隔1秒将缓冲区的数据写到AOF文件(默认方案)

写完命令执行完后先放入AOF缓冲区,由操作系统决定何时将缓冲区的数据写到AOF文件

 

always       同步刷盘            可靠性高,几乎不丢数据    性能影响较大

everysec   每秒刷盘             性能适中                              最多丢失1秒数据         

no              操作系统控制      性能最好                             可能丢失大量数据

 

AOF是记录命令,AOF文件比RDB文件大的多,而且会记录对同一个key的多次写操作,但是只有最后一次写操作才有意义。通过执行bgrewriteaoof命令,可以让AOF文件执行重写功能,用最少得命令达到相同效果。

 

7.数据过期策略

惰性+定期 配合使用

惰性删除:设置该redis过期时间后,不去管塔,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

 

定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的key进行检查,并删除其中过期的key)

                  SLOW 定时任务 执行频率固定

                  FAST   执行频率不固定

优点:通过限制删除操作执行的时长和频率减少删除操作对CPU的影响,定期删除也可以有效释放过期key占用的内存。

缺点:难以确定删除操作执行的时长和频率。

 

8.数据淘汰策略

数据淘汰策略:当redis中的内存不够用时,此时在向redis中添加新的key,那么redis就会按照某以规则将内存中的数据删除。

8种不同的淘汰策略

不淘汰任何key,但内存满时不允许写入新数据,默认策略,内存满了 继续添加数据 会报错。

LRU算法:最近最少使用,用当前时间减去最后一次访问时间。

LFU算法:最少频率使用。

或者使用随机淘汰。

还可以 将顶置数据不设置过期时间,同时使用LRU算法。

 

9.redis分布式锁 使用场景

集群情况下的定时任务,抢单,幂等性场景

 

线程一:1.获取互斥锁成功   2.查询优惠券  3.库存是都充足  是:扣除库存 否:抛出异常  4.释放锁

线程二:1.获取互斥锁失败  重试  重试 重试  2.查询优惠券  3.库存是都充足  是:扣除库存 否:抛出异常

 

为了支持并发请求,会使用集群部署。使用ngix,进行反向代理,负载均衡到几个服务器上。不能使用本地锁,需要使用分布式锁。

 

10.redis分布式锁 实现原理

redis实现分布式锁主要利用redis的setnx命令。

如果不设置超时时间,当业务超时或者服务宕机就没有办法释放锁了。

redis分布式锁要合理控制锁的有效时长:给锁续期。

 

redisson实现分布式锁 执行流程

加锁成功后  有一个看门狗 每隔 release Time/3  对锁进行续期。业务完成,去手动释放锁,并通知看门狗不续期。

如果加锁失败  会进行while循环,不断尝试加锁。

redisson所有命令lua脚本实现 保证原子性。

 

redisson实现分布式锁 可重入

同一个线程可以多次获取同一把锁,而不会造成死锁。每次加锁,内部会维护一个计数器(或持有次数),只有当释放次数等于加锁次数时,锁才会真正被释放。

利用hash结构记录线程id和重入次数。

 

redisson实现分布式锁 主从一致性

 

                                             从节点(redis slave)

主节点(redis master)

                                             从节点(redis slave)

redlock(红锁):不能只在一个redis实例上创建锁,应该在多个redis实例上创建  (n/2+1),避免在一个redis实例上加锁

缺点:实现复杂 性能差 运维繁琐

如果一定要保证数据的强一致性,建议采用zookeeper实现分布式锁

 

11.主从复制,主从同步

三种集群方案

1.主从复制

2.哨兵模式

3.分片集群

 

主从复制:单节点redis的并发能力有上限,进一步提高并发能力,需要搭建主从集群,实现读写分离。

主节点进行写操作,从节点进行读操作。主节点进行写操作后,将数据同步到从节点

 主从数据同步原理

主从全量同步

1.执行replicao命令,建立连接

2.请求数据同步

3.判断是否是第一次同步

4.是第一次,返回master的数据版本信息 replid offset

5.保存版本信息

6.执行bgsave,生成RDB文件

7.发送RDB文件

8.清空本地数据,加载RDB文件

9.记录RDB期间的所有命令

10.发送repl_baklog中的命令

11.执行接收到的命令

 

主从增量同步

1.slave重启

2.psync repild offset

3.判断请求replid是否一致

4.不是第一次,回复continue

5.去repl_baklog获取offset后的数据

6.发送offset后的数据

7.执行命令

 

12.哨兵模式(保证redis高并发可用)

监控:Sentinel会不断检查 master和slave是否按预期工作。

自动故障恢复:如果master故障,sentinel会自动将一个slave提升为master。故障恢复后,也以新的master为主。

通知:被监控的 Redis 实例出现问题时,Sentinel 可以通过 API(如发送邮件、调用脚本)通知管理员。

 

Sentinel基于心跳机制检测服务状态,每隔1秒向集群的每个实例发送ping命令

1.主观下线:Sentinel节点发现某实例为在规定时间响应,则认为该实例 主观下线

2.客观下线:超过指定数量(quorim)的sentinel都认为该实例主观下线,则认为该实例 客观下线

 

选主规则:

1.首先判断主与从节点断开时间长短,超过指定值就排除该从节点

2.然后判断从节点的slave-prioity,越小优先级越高

3.如果slave-prioity一样,则判断slave节点的offset值,越大优先级越高

4.最后判断slave的运行id大小哦,越小优先级越高

 

脑裂问题

集群脑裂是由于主节点和从节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到主节点,所以通过选举的方式提升了一个从节点为主,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在老的主节点那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将老的主节点降为从节点,这时再从新master同步数据,就会导致数据丢失。

解决:我们可以修改redis的配置,可以设置最少的从节点数量以及缩短主从数据同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失。

 

13.分片集群

主从和哨兵可以解决高可用,高并读的问题。但是依旧存在 两个问题。

海量数据存储问题。高并发写的问题。

集群中有多个master,每个master保存不同数据。

每个master都可以有多个slave节点

master通过ping检测彼此健康状态

客户端请求可以访问集群任意节点,最终都会被转发到正确节点

 

分片集群引入了哈希槽的概念,redis集群有16384个哈希槽,每个key通过crc16校验后 对16384进行取模。决定放在哪个槽,每个节点负责一部分的hash槽

 

14.redis是单线程,为什么执行速度很快

redis是纯内存操作,执行素的非常快

采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑安全问题

采用I/O多路复用模型,非阻塞IO

 

I/O多路复用

redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行素的,I/O多路复用就是实现了高效的网络请求

利用单个线程同时监听多个Socket,可读可写时得到通知,充分利用CPU资源

监听方式:select poll epoll

 

特性select/pollepoll
通知方式 告诉你“有连接就绪” 告诉你“哪个连接就绪”
需要操作 用户需遍历所有 fd 查看哪个就绪 直接返回就绪的 fd 列表
效率 O(n) 遍历开销大 O(1) 或 O(k),k 是就绪数

 

redis网络模型

IO多路复用+事件派送

Redis 使用一个 事件循环(event loop) 来处理两类事件:

  1. 文件事件(File Events)

    • 对应网络 I/O:客户端连接、读请求、写响应等
    • 底层通过 epoll(Linux)、kqueue(macOS/BSD)或 select(兼容模式)实现 I/O 多路复用
  2. 时间事件(Time Events)

    • 定时任务:如过期键清理、持久化触发(RDB/AOF)、集群心跳等

 

posted @ 2025-11-07 23:13  财神给你送元宝  阅读(5)  评论(0)    收藏  举报