文章目录
redis单线程为什么那么快
- 在内存中操作
- 核心是基于非阻塞的I/O多路复用机制(主进程只会处理命令,不会进行磁盘IO操作)
- 单线程反而避免了多线程频繁上下文切换带来的性能问题
redis分布式锁底层是如何实现的
过期键的删除策略
惰性过期
访问时采取判断是否过期(有点类似于延迟加载,对内存不友好,但是大量节省了cpu资源)
定期过期
隔一段时间去扫描一下key(每次扫描一定数量的key,而非全部扫描),这里要注意考虑执行频率和执行
Redis的持久化机制
机制一
RDB(Redis Database)
每隔指定时间,fork一个子进程,将当前数据库快照写入临时文件,写入完成后,再替换原始文件。用二进制方式压缩存储写入磁盘,存的是二进制数据
- 触发方式
- 手动触发
- save命令,使用此命令时,会使主进程处于阻塞状态,直到RDB持久化完成,才会响应其它客户端的命令
- bgsave命令,fork出一个子进程,父子有一块共享的内存区域,主进程只会在fork时有短暂阻塞,之后就可以响应其它响应
- 自动触发
- save m n:是说在m秒内如果有n个键发生改变,则自动触发bgsave
- flushall: 清空redis的所有数据库,(flushdb,清空当前数据库),同时清空RDB文件,之后生成dump.rdb(内容为空)
- 主从同步:全量同步时才会触发
RDB优点
- 只有一个dump.rdb文件,方便持久化
- 容灾性好,方便备份
- 性能最大化,fork子进程完成写操作,实现IO最大化
- 数据集比较大的时候,效率比AOF启动效率/恢复速度要高
RDB缺点
- 数据安全性低:因为每隔一段时间做一次持久化,如果两次中间redis发生故障,则会有数据丢失
- fork子进程会占用cpu
机制二
AOF(append only file)
每执行一次增、删、改操作(查操作不会存储),就把该命令追加到log文件中,存的是命令
- 首先,命令放入AOF缓冲区(不是直接全部写入log)
- 缓冲区根据同步策略向硬盘进行同步操作
- 随着AOF文件越来越大,需要对文件进行重写,达到压缩目的
- 同步策略:
- 每秒同步:如果两次之间宕机,会丢失一秒的数据
- 每修改同步:如果宕机会丢失一条数据层
- 不同步(同步策略我们自己不管,交给操作系统处理) :会丢失较多数据
AOF优点
- 数据安全(同步策略)
- 通过append写文件,即使中途宕机了,也不会破坏已存在的内容,redis提供了redis-check-aof工具来解决数据一致性问题
- 定期rewrite,达到压缩目的
AOF缺点
- 比RDB文件大,恢复速度慢
- 数据集大的时候,比RDB启动效率低
Redis集群方案(高可用方案)
一 主从复制模式(解决HA单点故障问题)
数据同步且数据全量一致
二 哨兵模式(基于主从模式来的)
主要做分布式协调,选主
- 集群监控
- 故障通知
- 故障转移
- 配置中心:通知客户端使用新的master地址
三 sharding(分片,分布式缓存寻址)
四 Cluster(分治分片,解决容量、压力、瓶颈问题,哈希槽)
Redis数据结构及使用场景
Redis主从复制
- 全量复制
- 部分复制
流程图
布隆过滤器
缓存穿透、缓存击穿、缓存雪崩
缓存穿透
在redis和数据库中都查不到。解决方案:
- 对参数进行合法性校验
- 把查不到的结果也放在redis中(比如我们查pro_id=-1的结果是null,那么redis中也存一条对应的记录),但会出现很多冗余无用key的情况,因此这一类缓存数据的有效期要设置的短一点
- 引入布隆过滤器
缓存击穿
缓存中没有,数据库有(一般针对于热点key)。解决方案:
- 设置"热点缓存"永不过期,这里注意:在value中有逻辑上的过期时间,然后另起一个线程,定期重建这些缓存
- 加载DB的时候,防治并发,即在DB往Redis写的时候加一个并发锁
缓存雪崩
缓存大面积同时过期导致请求都被转发到DB。解决方案:
- 把失效时间分散开(比如在原来统一过期时间基础上加一个随机值)
- 热点数据永不过期
Redis分布式锁的实现
1、setnx+setex(加锁+设置过期时间)
会存在死锁问题如果setnx完成但还没做setex时宕机
2、set(key, value, nx, ex)
将setnx+setex二合一,保证原子性
3、分布式锁可能存在的问题
- 1、任务超时时,锁会自动释放,导致并发(比如设置的过期时间是5s,但是任务10s还没完成)
- 解决方法:这里我们可以使用redission(看门狗)进行自动续期来解决
- 2、加锁和释放锁不是同一个线程操作的(比如过期时间是5秒,任务A执行8s,第5s时锁已释放并紧接着被任务B重新加锁,那么任务A执行完之后在不知道自己超时的情况下它去删除锁,就会出现问题)
- 解决方法:在value中,存入uuid线程唯一标识,在删除锁的时候判断该标识(使用lua脚本保证原子操作)
- 3、不可重入,使用redission解决(
AQS机制
实现的计数器)- 4、异步复制的时候可能造成锁丢失,使用
redLock红锁机制
解决
- 顺序向5个节点申请加锁
- 根据一定的超时时间来判断是不是跳过该节点
- 三个节点加锁成功且花费时间小于锁的有效期
- 认定为加锁成功
Redis事务的实现
如何保证缓存和数据库的一致性
1、先删缓存再更新数据库。
存在的问题
- 高并发状态下,线程A删除了缓存、还没写数据库,此时线程B来读发现缓存中没有于是去数据库读,读到的就是旧数据,此时A写完数据库并且更新了缓存,结果B在读到旧数据之后再次更新了缓存,就会出现**脏数据**
解决方案
- 1 先操作缓存但是不删除,而是把value设置成一个业务上不会用到的值,比如-999。此时B来的时候发现是-999,那么就休眠一会儿再来读 - 2 `延时双删`。先删除缓存、再写数据库、休眠一会儿再删除缓存
Redis如何实现延时队列?
Redis跳表怎么实现?
参考资料:都在问的Redis高频面试题