Redis八股学习记录1Redis简介与数据结构from小林coding
- Redis简介
- Redis数据结构
- String底层实现
- List底层实现
- Hash底层实现
- Set底层实现
- Zset
- Redis线程模型
- Redis持久化
- AOF日志的实现
- AOF日志重写机制
- RDB快照实现
- 混合持久化
- Redis集群
- 主从复制
- 哨兵模式
- 切片集群模式
- Redis的集群脑裂问题
- Redis过期删除与内存淘汰
- 惰性删除
- 定期删除
- Redis持久化时,对过期的键如何处理
- Redis主从模式中,对过期键如何处理
- Redis处理内存满了的问题
- Redis缓存设计
- 缓存雪崩问题
- 缓存击穿问题
- 缓存穿透问题
- 如何设计缓存策略,可以动态缓存热点数据
- Redis常见的缓存更新策略
- Cache Aside
- Redis实战
- Redis如何实现延迟队列
- Redis的大key如何处理
- 大Key的影响
Redis简介
开源的内存数据结构存储,读写速度快,用于缓存,消息队列,分布式锁等场景
与Memcached的相同点:
- 都是内存数据库,一般都用作缓存
- 都有过期策略
- 性能高
与memcached的不同点:
- Redis支持的数据结构更加丰富,memcached只支持最简单的key,value结构
- Redis支持数据的持久化,Memcached没有持久化功能
- Redis原生支持集群模式,Memcached没有原生的集群模式
- Redis支持发布订阅模型,Lua脚本,事务,memcached不支持
Redis数据结构
结构类型 | 结构存储的值 | 结构的读写能力 |
---|---|---|
string | 字符串,整数或浮点数 | 字符串操作,整数的自增自减 |
List | 链表,链表上每个都是字符串 | 两端的push和pop操作 |
set集合 | 包含字符串的无序集合 | 字符串集合,检查字符串是否在集合中,添加,删除,获取 |
hash | 键值对的无序集合 | 添加,删除湖片区元素 |
Zset | 键值对的有序集合 | 字符串与浮点数的有序集合,排列由分数的大小确定 |
String底层实现
SDS(简单动态字符串)
- 保存文本数据的同时,也能保存二进制数据
- 获取字符串长度的时间复杂度是O(1)
- SDS api安全,拼接字符串不会造成缓冲区溢出
List底层实现
底层由双向链表或压缩链表(小于512且元素小于64字节)实现
Hash底层实现
压缩列表(<512且所有值小于64字节)或者哈希表实现
Set底层实现
小于512整数集合,>=512哈希表
Zset
压缩列表或跳表实现
Redis线程模型
Redis单线程指的是[接受客户端请求->解析请求->进行数据读写->发送数据给客户端]是由一个线程完成的
整个Redis程序并不是单线程的,Redis程序会启动后台线程.三种线程[关闭文件,AOF刷盘,释放内存]
redis的reactor模型
Redis6.0版本之后,可以启动额外的网络IO线程,默认情况下会启动7个线程
- Redis Server:redis的主线程,主要负责执行命令
- bio_close_file,bio_aof_fsync,bio_lazy_fre:三个后台线程,分别处理异步关闭文件任务,AOF刷盘任务,释放内存任务
-io_thd1-3:三个IO线程,分担Redis网络IO的压力
Redis持久化
Redis持久化有三种策略:
- AOF日志:每执行一条写操作的命令,就把该命令以追加的形式写入到一个文件中
- RDB快照:某一时刻的数据,以二进制方式写入磁盘
- 混合持久化的方式:Redis4.0新增的方式,集成了AOF和RDB的优点
AOF日志的实现
Redis执行完一条写操作之后,把该命令以追加的方式写到文件中,Redis重启后,会读取该文件记录的命令,然后逐一执行的方式恢复数据
Redis采用先写入内存再写入AOF日志的策略原因如下
- 避免额外的检查开销:当语法命令有问题的时候.避免错误的命令记录到AOF中
- 不阻塞当前写操作命令的执行:因为写操作成功后,才将命令写入到AOF中
当然这样做也会带来如下的风险
- 数据可能丢失
Redis写入AOF日志的流程如下图
Redis提供了三种写回磁盘的策略,控制的是IO系统调用的过程,三种策略分别为Always,Everysec,No,介绍如下
- Always:每次写操作后,都会将AOF数据写到磁盘
- Everysec,每秒写一次磁盘
- No,由操作系统控制写回的时机
从上到下,安全性递减,性能递增
AOF日志重写机制
AOF日志是一个文件,随着写命令增加,文件大小会增加,Redis为了避免AOF文件越来越大,提供了AOF重写机制
Redis的AOF重写是由后台的子进程完成的,那如何控制主进程和后台子进程的AOF一致性呢,Redis设置了一个AOF重写缓冲区,在创建子进程时使用这个缓冲区,流程如下图
也即是,在子进程重写时,开启AOF重写缓冲区,写入命令将会同时写入到AOF缓冲区和AOF重写缓冲区,当子进程完成重写之后,向主进程发送一条信号,主进程将AOF重写缓冲区的内容追加到新的AOF文件中,之后将这个新的AOF覆盖掉原有的AOF文件,完成AOF重写
RDB快照实现
因为AOF日志存储的是操作的命令,并不是实际的数据,因此用AOF做故障恢复时,需将日志全部执行一遍,AOF日志过多的情况下,Redis的恢复将十分缓慢
为了解决这一个问题,提出了RDB快照,RDB快照记录的时某一个瞬间内存中的内容,RDB记录同样会创建子进程,并且由于写时复制,这个时候原本的父进程依然可以执行写操作
混合持久化
RDB优势在于数据的恢复块,但是可能丢失数据
AOF数据丢失少,但是恢复慢
Redis 4.0之后混合了AOF日志和内存快照,也就是说AOF文件前半部分时RDB格式的全量数据,后半部分是AOF的增量数据
混合持久化优点:
- 混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了大量数据丢失的风险。
混合持久化缺点:
- AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;
- 兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了
Redis集群
Redis集群设计原因:需要高可用的Redis服务
主从复制
主从复制是Redis高可用的基础保证,实际上就是将一台Redis服务器的数据同步到多台从Redis服务器上,即一主多从的模式,且主从服务器之间采用的是读写分离的方式
Redis的异步主从复制是无法实现强一致性保证的
哨兵模式
使用Redis主从服务的时候,若主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis增加了哨兵模式,哨兵模式可以监控主从服务器,并且提供主从节点之间的故障转移功能
切片集群模式
当Redis缓存的数据量大到一台服务器无法缓存时,就需要使用Redis切片集群,将数据分布到不同的服务器上,降低对单主节点的依赖,提高redis服务的读写性能
Redis Cluster方案采用哈希槽来处理数据和节点之间的映射关系,共16384个哈希槽
Redis的集群脑裂问题
情景:在一个网络波动的情况下,Redis有一个主节点A,和从节点BCD,以及哨兵E,当A与BCDE之间网络故障,但与客户端之间网络联通,这个时候客户端往A写数据,A无法同步到BCD,E发现异常并选举B作为新的主节点,在这之后网络恢复正常,并且B是新的主节点,将数据全量同步给ACD,这个时候A将清空自己的数据,并且接受B的数据,此时客户端的更新失效,产生数据丢失
解决方案:当主节点发现从节点下线或者通信超时的总量小于阈值时,禁止主节点写数据,直接把错误返回给客户端
Redis中有两个参数可以设置
- min-slaves-to-writes x :主节点必须要有x个从节点连接,小于这个数,主节点禁止写数据
- min-slaves-max-lag x:主从数据复制和同步的延迟不能超过x秒
Redis过期删除与内存淘汰
Redis可以对key设置过期时间,相应的机制被称为过期键值删除策略,每当对一个key设置了超时时间,Redis将会把该Key带上过期时间存储到一个过期字典中,可就是说过期字典保存了所有key的过期时间
查询一个key时,先查询是否在过期字典中
- 不在的情况,直接读取键值
- 存在的情况,获取key的过期时间,然后对比系统时间,若比系统时间大,则没有过期,否则判断过期
Redis使用的过期删除策略为惰性删除和定期删除
惰性删除
不主动删除Key,每次从数据库访问key时,都检测key是否过期,如果过期删除该key
优点:策略对CPU时间友好
缺点:浪费内存空间
定期删除
每隔一段时间从数据库取出一定数量的key进行检查,删除其中过期的key
优点:限制删除操作时长和频率,减少删除操作对CPU的影响,同时尽量节约空间
缺点:难以确定删除时长和频率,执行太过频繁对CPU不友好
Redis持久化时,对过期的键如何处理
Redis持久化时,文件有两种表现形RDB和AOF,过期键在这两种格式下的呈现状态如下
RDB分为两个阶段:
- RDB文件生成阶段:内存状态持久化生成RDB文件时,对Key进行过期检查,过期的key将不被保存到RDB文件中
- RDB文件加载阶段:RDB加载阶段,看服务器是主服务器还是从服务器,主服务器的情况下:载入RDB时,过期检查,如果过期不会被加载到数据库中,从服务器的情况下:不论是否过期都加载
AOF分为两个阶段,写入阶段和加载阶段:
- 写入阶段:当Redis以AOF模式进行持久化时,若数据库的某个过期键未被删除,那么AOF文件会保留此过期键,当过期键被删除之后,Redis会向AOF文件追加一条DEL命令显式删除该键值
- AOF重写阶段:执行重写时,进行过期检查,已过期的键不会保存到重写后的AOF文件中
Redis主从模式中,对过期键如何处理
Redis运行在主从模式下时,从库不会进行过期扫描,从库对过期的处理是被动的,也就是依赖主库的控制,主库的Key在到期时,会在AOF文件里增加一条DEL指令,同步到所有的从库
Redis处理内存满了的问题
Redis的运行内存达到了某个阈值,就会触发内存淘汰机制,阈值是设置的最大运行内存,淘汰策略有八种,主要是依照LRU和LFU策略设计的
Redis缓存设计
Redis使用典型场景
缓存雪崩问题
Redis数据过期之后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,这时候需要访问数据库,并将数据库更新到Redis后,以后的请求可以直接命中缓存
缓存雪崩问题是指:大量的缓存在同一时间过期时,若有大量的用户请求,都无法在Redis中处理,所有的请求全部访问数据库,导致数据库的压力骤增,严重的导致数据库宕机,从而导致一系列的连锁反应,造成整个系统崩溃,这就是缓存雪崩问题
缓存雪崩问题的解决方案:1.将缓存失效的时间随机打散2.设置缓存不过期
缓存击穿问题
缓存击穿问题是指,业务有几个数据会被频繁的访问,如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,转而直接访问数据库,数据库很容易高并发的请求冲垮,这就是缓存击穿问题
缓存击穿避免方式:
- 互斥锁,设置一段时间仅有一个业务线程请求访问缓存
- 不给热点数据设置过期时间
缓存穿透问题
发生缓存雪崩或者击穿时,数据库中依然保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,当用户访问的数据,既不在缓存中,也不在数据库中,导致请求访问缓存时,发现缓存缺失,转而访问数据库,而数据库中也没有要访问的数据,没办法构建缓存数据,当有大量的请求到来时,数据库压力剧增,这就是缓存穿透问题
缓存穿透发生有两种情况
- 业务误操作:缓存中的数据和数据库中的数据都被误删除了,导致缓存和数据库中都没有操作
- 黑客恶意攻击:大量访问某些不存在数据的业务
缓存穿透的应对措施三种
- 非法请求的限制,在api的入口处判断请求参数是否合理,判断出恶意请求需要直接返回错误,避免进一步访问缓存和数据库
- 设置空值或者默认值 :当线上业务发现缓存穿透时,缓存这个空值
- 设置布隆过滤器判断数据是否存在,避免查询数据库来判断数据是否存在
如何设计缓存策略,可以动态缓存热点数据
将一部分热点数据缓存起来,策略的思路是,通过数据的最新访问时间来做排名,过滤掉不常访问的数据,留下经常访问的数据
Redis常见的缓存更新策略
三种常见的缓存更新策略
- Cache Aside:旁路缓存策略
- Read/Write Through(读穿/写穿)策略
- Write Back(写回策略)
在实际开发中,使用的是Cache Aside,另外两种策略应用不了
Cache Aside
写策略的步骤:
- 先更新数据库中的内容,再删除缓存中的数据
读策略的步骤:
- 如果命中数据,那么直接返回数据
- 如果数据没命中缓存,那么从数据库中读取数据,然后将数据写到缓存中,并返回给用户
Redis实战
Redis如何实现延迟队列
延迟队列类似于定时器,那么Redis用ZSet加上轮询队列,处理回调即可完成延迟队列的实现
Redis的大key如何处理
大key是指Redis的key对应的value很大(元素多或者单个元素占用的空间很大)
大Key的影响
- 客户端超时阻塞.Redis是单线程处理,因此单个的大Key操作会比较耗时,那么就会阻塞Redis,从客户端来看,就是很久都没有响应
- 引发网络阻塞.每次大key产生的流量比较大
- 阻塞工作线程,如果删除大key时,会阻塞工作线程,导致没法处理后续的命令
- 内存分布不均