面试知识点-redis
2021-10-10 22:34:56 89 举报
AI智能生成
redis知识一网打尽
作者其他创作
大纲/内容
redis持久化
RBD
是对 redis 中的数据执行周期性的持久化。定时将当前时刻的所有数据保存到一个RDB文件中
优点
RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。
载入时恢复数据快、文件体积小。
缺点
会一定程度上丢失数据(因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。)
每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。 虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。
AOF
将执行的写命令以 append-only 的模式写入到到AOF文件中。
优点
丢失数据少(默认配置只丢失一秒的数据)。
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
AOF 文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行 seek,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损
缺点
恢复数据相对较慢,文件体积大
数据恢复的时候可能会有bug。AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。作为对比,RBD不会出现这种问题
两种方式的选择
不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
缓存
缓存穿透
缓存穿透是指查询一个一定不存在的数据,会导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决方式
缓存空对象,对于每次没有查找到的请求,设置一个空缓存
布隆过滤器
将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间被删除或者是redis宕机,导致大量的查询请求直接走数据库查询,导致服务器瘫痪
解决方式
大量数据到期被删除
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
设置热点数据永远不过期。
redis宕机
事发前
实现redis高可用性(主从架构+哨兵/redis集群)
事发时
本地缓存(ehcache)+限流(hystrix)&降级
对于一个请求,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。
限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求可以走降级,可以返回一些默认的值,或者友情提示,或者空白的值。
好处
数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。
事发后
redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
缓存击穿
说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
解决方式
可以将热点数据设置为永远不过期
基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
缓存和数据库双写一致性问题
缓存和数据库双写一致问题指的是数据库的数据跟缓存的数据不一致
Cache Aside Pattern
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
会导致初级读写不一致问题
初级读写不一致问题
先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
解决办法
先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
应用场景
在并发量不高的情况下,可以采取先删除缓存,再更新数据库的方式
复杂的读写不一致问题
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。
解决办法
读取数据时,根据数据的唯一标识符,将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。
优化点
一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
应用场景
在并发量较高的情况下,可以采取这种串行化方式
注意点
需要每个读请求必须在超时时间范围内返回,否则会导致大量读请求直接走数据库
布隆过滤器
redis的单线程和速度
redis速度快的原因
1、Redis 将数据储存在内存里面,操作完全基于内存,绝大部分请求是纯粹的内存操作,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度极快。
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗)
使用的命令是epoll,select,poll
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
redis为什么是单线程
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了
这里我们一直在强调的单线程,只是在处理我们的网络请求的时候只有一个线程来处理,一个正式的Redis Server运行的时候肯定是不止一个线程的
因为是单一线程,所以同一时刻只有一个操作在进行,所以,耗时的命令会导致并发的下降,不只是读并发,写并发也会下降。而单一线程也只能用到一个CPU核心,所以可以在同一个多核的服务器中,可以启动多个实例,组成master-master或者master-slave的形式,耗时的读命令可以完全在slave进行。
基本数据结构
String
基本操作
set key value
底层实现
SSD
使用场景
常规统计
微博数
分数数
在线人数
hash
基本操作
hset key value
hget key value
底层实现
ziplist
dict
本质上就是一个hashmap
使用场景
对象存储修改
用户信息存储
set
基本操作
sadd mySet 1
smembers mySet
sismember mySet 3
srem mySet 1
scard mySet
sinter yourSet mySet
底层实现
intset
dict
使用场景
1.去重的场景
某段时间内访问的不重复的ip数
2.取交集并集的场合
计算共同好友
list
基本操作
lpush mylist 1
rpop mylist
底层实现
linkedList
ziplist
使用场景
列表相关数据
粉丝列表
关注列表
消息队列
分页查找
排行榜
取最新的n个数据
zSet
底层实现
skiplist
跳表是一个典型的以空间换时间的数据结构
跳表是一个多层双向链表,能够实现在链表上进行二分查找,因此时间复杂度为O(logn)
每一层的节点个数是下一层节点个数的一半,最底层的链表是原始链表
查找的时候,由上到下开始查询
插入
1.通过二分查找查找到应该插入的位置并插入节点
2.通过随机方式判断该节点是否需要插入到上层链表,结果为“正”则提升并继续进行判断,结果为“负”则停止。
时间复杂度为O(logn)
删除
1.通过二分查找查找到该节点第一次出现的位置
2.删除从这层链表开始的下面每一层的该节点
时间复杂度为O(n)
ziplist
是由一系列的特殊编码的连续内存块组成的顺序性数据结构,有点类似数组
当节点个数小于128或者所有节点的大小都小于64字节时,使用ziplist,否则使用skiplist
基本操作
zadd
zset
zrem
使用场景
优先级消息队列
排行榜
redis高可用性
主从架构
复制
全量复制
1.当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。
如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。
如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。
2. master同时 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave
3.slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。
注意点
1.如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
2.slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时基于旧的数据版本对外提供服务。
3.如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。
增量复制
master 每次接收到写命令之后,先在内部写入数据,然后异步发送给 slave node。
断点续传
从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
原理
master node 会在内存中维护一个 backlog,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会向 master发送一个sync请求,master会根据slave发送的psync请求中的 offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 resynchronization。
异步复制
master 每次接收到写命令之后,先在内部写入数据,然后异步发送给 slave node。
一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。
异步复制
master 每次接收到写命令之后,先在内部写入数据,然后异步发送给 slave node。
哨兵机制
Redis提供了哨兵(Sentinal)机制来解决主服务器挂掉的情况。如果主服务器挂了,我们可以将从服务器升级为主服务器,等到旧的主服务器重连上来,会将它变成从服务器。
哨兵的功能
1.Sentinel不停地监控Redis主从服务器是否正常工作
发送心跳包
2.如果某个Redis实例有故障,那么哨兵负责发送消息通知管理员
3.如果主服务器挂掉了,会自动将从服务器提升为主服务器(包括配置都会修改)。
4.Sentinel可以作为配置中心,能够提供当前主服务器的信息。
主备切换过程
1.启动和初始化Sentinel
Sentinel本质上只是一个运行在特殊模式下的Redis服务器,它和普通redis服务器启动是有区别的,比如不会加载RDB或者AOF文件
Sentinel会创建两个连向主服务器的网络连接:
命令连接(发送和接收命令)
订阅连接(订阅主服务器的_sentinel_:hello频道)
命令连接(发送和接收命令)
订阅连接(订阅主服务器的_sentinel_:hello频道)
2.获取和更新信息
Sentinel通过主服务器发送INFO命令来获得主服务器属下所有从服务器的地址信息,并为这些从服务器创建相应的实例结构。
当发现有新的从服务器出现时,除了创建对应的从服务器实例结构,Sentinel还会创建命令连接和订阅连接。
在Sentinel运行的过程中,通过命令连接会以每两秒一次的频率向监视的主从服务器的_sentinel_:hello频道发送命令(主要发送Sentinel本身的信息,监听主从服务器的信息),并通过订阅连接接收_sentinel_:hello频道的信息。
这样一来一回,我们就可以更新每个Sentinel实例结构的信息。
这样一来一回,我们就可以更新每个Sentinel实例结构的信息。
3.判断主服务器是否下线了
主观下线
Sentinel会以每秒一次的频率向与它创建命令连接的实例(包括主从服务器和其他的Sentinel)发送PING命令,通过PING命令返回的信息判断实例是否在线
如果一个主服务器在down-after-milliseconds毫秒内连续向Sentinel发送无效回复,那么当前Sentinel就会主观认为该主服务器已经下线了。
客观下线
当Sentinel将一个主服务器判断为主观下线以后,为了确认该主服务器是否真的下线,它会向同样监视该主服务器的Sentinel询问(通过Sentinel_:hello频道获取),看它们是否也认为该主服务器是否下线。
如果足够多的Sentinel认为该主服务器是下线的,那么就判定该主服务为客观下线,并对主服务器执行故障转移操作。
在多少毫秒内无效回复才认定主服务器是主观下线的,以及有多少个Sentinel认为主服务器是下线才认定为客观下线。这都是可以配置的
4.选举领头Sentinel和故障转移
选举领头Sentinel的规则也比较多,总的来说就是先到先得(哪个快,就选哪个)
故障转移
1.在已下线主服务器属下的从服务器中,挑选一个转换为主服务器
2.让已下线主服务器属下的所有从服务器改为复制新的主服务器
3.已下线的主服务器重新连接时,让他成为新的主服务器的从服务器
挑选某一个从服务器作为主服务器的依据
1.跟master断开连接的时长
2.slave优先级
3.复制offset
4.run id
过期策略
定期删除+惰性删除
定期删除
系统每隔一段时间,随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
惰性删除
在获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
内存淘汰机制
原因:如果定期删除漏掉了很多过期 key,然后也没及时去查,也就没走惰性删除,会导致大量过期 key 堆积在内存里,导致 redis 内存耗尽
解决方法
noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
0 条评论
下一页