1.Redis有哪些数据类型
- 常用的数据类型,String,List,Set,Hash和ZSet(有序)
- String:Session,Token,序列化后的对象存储,BitMap也是用的String类型,
- BitMap:不仅可以用做布隆过滤器,也经常用于记录签到打卡,方便记录打卡的连续天数。
- ZSet:微信步数排行榜,段位排行榜,可以用Redis里的相关命令解决,底层用的是跳表,实现简单,插入删除快,区间查询快。
- Set的应用场景:存放的数据不重复,文章和动态点赞
2.Redis内存管理(数据淘汰策略)
- 为什么要数据淘汰策略:会给缓存数据设置一个过期时间,如果一直查询数据,存入到内存里面,会导致out of memory。比如expire key 60,60秒过后过期。还有比如验证码60秒失效,也可以用过期时间。
- Redis的内存淘汰机制
- volatile-lru(业务中有置顶数据,例如博客置顶),从已设置过期时间的数据集中选择最近最少使用的淘汰。
- volatile-ttl,从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-lfu(有短时高频访问的数据),从已设置过期时间的数据集中挑选最不常用的key。
- volatile-random,从已设置过期时间的数据集中随机挑选
- allkeys(所有的缓存数据中,当内存不足时),allkeys-lru(业务没有冷热区分),allkeys-random,allkeys-lfu
- noeviction(默认):禁止驱除数据,当内存不足时,新写入的操作会报错。
3.Redis生产问题
缓存穿透:就是大量请求的key不合理,当我们查询数据库时,会先去查询缓存,缓存没有,再去查数据库,但数据库也没有,这样会对数据库造成很大的压力。(穿透就是有些能过去,有些过不去)
- 场景:比如黑客估计创造了一些非法的key,导致大量请求落到数据库
- 解决一:布隆过滤器,将所有可能得到的值先放到布隆过滤器中(位数组和随机映射函数),当用户请求过来,先判断用户发来的请求值是否存在布隆过滤器中。不存在直接返回错误信息,如果存在则判断缓存中是否有数据,没有就去数据库查,然后插入到缓存,并返回给用户。
- 解决二:接口限流,对异常频繁访问的行为,直接将ip拉入黑名单
缓存击穿:在请求大量的热点数据,但这部分数据在缓存中已经失效,然后会将大量的请求打在数据库上,给数据库带来巨大的压力。(这部分热点数据都失效了)
- 场景:比如秒杀活动,某个秒杀商品的数据在缓存中突然过期,就会大量的线程对该商品请求落到数据库上。
- 解决一:设置过期时间长,逻辑过期,高可用,不保证数据绝对一致
- 解决二:设置热点数据提前预热,将其放到缓存并设置合理过期时间
- 解决三:在请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求落到数据库上。强一致,性能差。
缓存雪崩:缓存在同一时间大面积失效,导致大量请求直接落到数据库上,对数据库造成了巨大的压力。
- 解决一:采用Redis集群(哨兵模式)
- 解决二:限流,Nginx和Spring cloud gateway
- 解决三:缓存预热,程序启动或在运行中,主动将热点数据加载到缓存中,(使用定时任务Spring Task,定时出发缓存预热逻辑)
- 解决四:给不同的key的TTL添加随机值。这样不会同一时间大面积失效。如何做到数据库和Redis缓存里面的数据库一致:(强一致性的业务)
- 延迟双删:
- 定义:延迟双删是一种用于处理高并发场景下缓存与数据库数据一致性的策略。它的核心思想是在更新数据库之前,先从缓存中删除旧数据,然后更新数据库,最后经过一段延迟时间后再次尝试删除缓存中的数据。
- 原因:在高并发场景下,直接更新数据库后立即删除缓存可能会导致读请求在缓存未更新前获取到脏数据。延迟双删通过引入延迟机制,给数据库更新留出时间,减少脏数据的产生。
- 分布式:读多写少的业务
- 共享锁:读锁readmeLock,加锁之后,其他线程可以共享读操作,但不能写操作
- 排他锁:独占锁writerLock,加锁之后,其他线程阻塞读写操作
- 读数据时添加共享锁,写数据时添加排他锁。
异步通知保证数据的最终一致性(短暂不一致是允许的),也可以用Canal的异步通知。
- 使用MQ中间件,更新数据之后,通知缓存删除。保证MQ的可靠性。
- 利用Canal中间件,不需要修改业务代码,伪装为MySQL的一个从节点,Canal通过读取binlog数据更新缓存。
Redis 持久化
- RDB(Redis数据快照),把内存中的所有数据记录到磁盘中,当Redis出现故障重启后,从磁盘读取快照文件,恢复数据。Redis内部有触发RDB机制,save 900 1,900秒内,如果至少有一个key被修改,就执行bgsave。
- RDB执行原理,bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据(页表)。完成fork后读取内存数据并写入RDB文件。
- fork技术采用copy-on-write技术:当进程执行读操作时,访问共享内存。当主进程执行写操作时,则会拷贝一份数据,执行写操作。
- 页表:记录虚拟地址与物理地址的映射关系,因为主进程不能直接访问物理内存,只能通过页表来访问。
- AOP(追加文件),Redis处理的每个写命令都会记录到AOF文件,可以看作命令日志文件。但是AOF文件默认是关闭的,需要修改redis.conf配置文件来开启AOF。
- appendonly yes 开启AOF
- appendsync always 表示每次执行一次写命令,同步刷新新盘,可靠性高,性能差
- appendsync everysec,性能适中,会丢失1s数据
- appendsync no 操作系统控制何时将缓冲区内存写回磁盘,性能最好,可能会丢失大量数据
- 缺点:AOF文件会比RDB文件大的多
- 解决办法:使用bgrewriteaof命令,让AOF文件执行重写功能,用最少的命令达到相同的效果。
#AOF文件比上次文件增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
#AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb数据过期策略
- set name heima 10(10秒后过期)
- 惰性删除
- 设置了key的过期时间后,在需要key时,检查是否过期,如果过期直接删除
- 对CPU友好,只有使用到key的时候才会去检查key
- 对内存不友好,如果一个key已经过期,但是一直没有使用,那么会一直存在内存中
- 定期删除
- 每过一段时间,对一些key进行检查,删除过期的key
- 定期清理的两种模式,FAST模式(不固定,两次清理时间不超过2ms,一次清理不超过1ms)和SLOW模式(定时任务,每次清理不超过25ms)。
- 惰性删除配合定期删除
- RDB和AOF各有优点,如果对数据安全性要求较高,会结合使用,下面是他们的对比
RDB | AOF | |
---|---|---|
持久化方式 | 定时对整个内存做快照 | 记录每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会丢失 | 相对完整,取决于刷盘策略 |
文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积大 |
宕机恢复速度 | 很快 | 慢 |
数据恢复优先级 | 低,因为数据完整性不如AOF | 高,因为数据完整性更高 |
系统资源占用 | 高,大量CPU和内存消耗 | 低, 主要是磁盘IO资源,但AOF重写时占用大量CPU和内存资源 |
使用场景 | 可以容忍分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求较高 |
4.Redis 分布式锁
- 如何实现:
- 背景:集群情况下的定时任务、抢单、幂等性场景。
- 出现异常的情况,当两个线程同时查询库存时,刚好库存有一张优惠卷,然后都判断是否需要扣减库存,判断通过库存变为-1,存在的超卖现象。
- 不能使用synchronized锁,synchronized锁可以解决同一个JVM下的线程互斥,解决不了多个JVM下的线程互斥,如果通过Nginx反向代理部署到多个服务器上,用synchronized锁不能解决问题。
- 分布式锁:如果获取锁成功,则会在分布式锁中添加一条记录,证明线程1中已经有锁了,其他线程会出现阻塞状态,直到释放锁,其他线程才能获取锁。形成互斥的效果
- 实现:setnx命令,set if not exists如果不存在就set
- 获取锁:set Lock value NX EX 10,添加锁,NX互斥,EX是过期时间(设置值和过期时间放在一条命令一起执行,因为执行该命令不保证原子性。加上失效时间避免死锁)
- 释放锁:del key
- 如何设置控制锁的有效时长,也就是当业务执行完成时,才释放锁,而不是还没执行完就释放了锁。
- 给锁续期:Redisson实现分布式锁,会另外开一个线程去监控watch dog(看门狗),是否需要给过期时间给续期。如果锁的过期时长是releaseTime=30s,则会每隔releaseTime / 3,也就是10s都会重新续期30s。默认是每个10秒续期一次。
- 同时Redisson还会设置一个while循环来等待该锁释放后,其他线程不断尝试获取锁。这样在高并发的情况下,增加分布式锁的使用性。lua脚本保证命令执行的原子性。
- 分布式锁是可重入的,多个锁重入需要判断是否是当前线程,在Redis中使用的hash结构来存储线程信息和重入的次数,value值存的是线程id和重入次数。
- 解决主从数据一致性的问题:RedLock红锁(不推荐,性能太低)。如果业务中的强一致性,使用zookeeper来实现分布式锁。
其他考点
- Redis集群有哪些方案?
- 主从复制:主节点负责写操作,其他节点负责读操作,并且要保证主节点将数据同步给从节点。以提高Redis的并发能力。master主节点,slave从节点。
- 主从全量同步:
- 主从增量同步(slave 重启或后期数据变化)
- 哨兵的作用:来实现主从集群的自动故障修复。主要是当主节点宕机后,就失去了写的操作。
- 监控,Sentinel会不断检查主节点和从节点是否按预期工作
- 自动故障修复:如果主节点出现故障,那么会将其中一个从节点升级为主节点。
- 通知:会将故障信息推送给Redis客户端
- 原理:
- 哨兵模式容易出现脑裂问题
- 总结
- 分片集群结构
- 主从和哨兵模式可以解决高可用和高并发读的问题
- 出现问题:不能解决海量数据存储和高并发写的问题。
- 解决方案:集群中采用多个主节点,每个主节点保存不同数据。每个master可以有多个salve节点。master之间通过ping检测彼此之间的状态。
- 哈希槽(Redis分片集群中数据是怎样存储和读取的?)
- Redis是单线程的,但为什么还那么快?
- Redis是纯内存操作的,而MySQL是存入磁盘,所以Redis执行速度非常快。
- 采用单线程,避免不必要的线程上下文切换
- 使用I/O多路复用模型,非阻塞IO
- Redis的瓶颈是网络延迟,I/O多路复用模型主要实现了高校的网络请求。
- 用户空间和内核空间:用户空间只能执行受限的命令,当要调用系统资源时,需要通过内核空间提供的接口访问。Linux为提高IO效率,会在用户空间和内核空间中加入缓存区,写数据:用户缓冲区-》内存缓存区-》硬件设备,读数据刚好相反。
- 阻塞IO模型:阻塞等待数据,阻塞读数据
- 非阻塞IO模型:recvfrom操作会理解返回结构,不会阻塞用户线程。(CPU使用率暴增)
- IO多路复用模型:select,poll和epoll
- Redis的网络模型
- 总结