这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
1、Redis 是单线程的吗?
Redis6.0版本之前的单线程是指网络的I/O和键值对的读写是由一个线程来完成的。
Redis6.0之后网络请求过程采用了多线程,而键值对读写命令依旧还是单线程。就是一个线程处理所有网络请求,其他模块仍用了多个线程。所以不需考虑并发安全性。
2、Redis单线程为什么还能这么快
- 命令执行基于内存操纵,一条命令在内存里操作的时间是几十纳秒。
- 单线程的话就能避免多线程的频繁上下文切换问题
- 基于非阻塞的IO多路复用模型机制:“多路”是指多个网络连接,“复用”是指同一个线程。多路I/O复用模型是利用select、poll、epoll可以同时监察多个流的I/O事件的能力,在空闲的时候会把当前线程阻塞掉,当有一个或多个流有IO事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是轮询那些真正发生了事件的流),并且只依此顺序的处理就绪的流,这种做法就避免了大量的无用操作。
- 高效的数据存储结构:全局的hash表以及多种高效的数据结构,比如:跳表,压缩列表,链表等等
3、为什么Redis要使用I/O多路复用机制呢?
因为Redis是单线程的,所有的操作命令都是顺序执行的,一旦有读写操作命令,等待用户输入或输出就会造成阻塞,所以I/O操作在一般情况下往往不能直接返回,这会导致某一操作的I/O阻塞导致整个进程无法进行其他操作,而I/O多路复用就是为了解决这个问题而出现的。
4、Redis底层数据结构是如何用跳表来存储的
跳表:将有序链表改造为支持近似“折半查找”效率的算法,可以进行快速的插入,删除,查找操作
5、Redis Key过期了为什么内存没释放
情况1:SET除了可以设置key-value之外,还可以设置key的过期时间
SET wanghui 1996 EX 120
此时如果修改key的值,如果没有加上过期时间的参数
SET wanghui 1997
TTL wanghui
-1
那这个key的过期时间就会被擦除,永远不会过期。
Redis对于过期key的处理一般有2种
- 惰性删除:当读或者写一个已经过期的key时,会触发惰性删除策略,判断key是否过期,过期了就直接删除
- 定时删除:惰性删除无法保证冷数据及时删除,所以Redis会定期删除(默认100ms)主动淘汰一批已经过期的key,而不是一次性全删除,这样会对服务器有很大压力。
所以会出现key过期了但时还没有被清理掉的情况
6、Key没设置过期时间为什么被Redis主动删除了
当Redis已用内存超过Maxmemory的限定时,会触发主动清理策略
a)针对设置了过期时间的key处理:
- volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
- volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
- volatile-Iru:会使用 LRU 算法筛选设置了过期时间的键值对删除。
- volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除。
b)针对所有的Key处理:
- allkeys-random:从所有键值对中随机选择并删除数据。
- allkeys-lru:使用LRU算法在所有数据中进行筛选删除。
- allkeys-lfu:使用LFU算法在所有数据中进行筛选删除。
c)不处理
- noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
7、Redis淘汰Key的LRU与LFU的区别
LRU算法(Least Recently Used,最近最少使用):淘汰很久没有被访问的数据
LFU算法(Least Frequently Used,最不经常使用):淘汰最近一段时间被访问最少次数的数据
绝大多数情况下使用的时LRU策略,当存在大量的热点缓存数据的时候,LFU会更好。
8、删除Key的命令会阻塞Redis吗
删除单个字符串类型的key,时间复杂度O(1)。这种情况的话,如果string很大,几百兆也是会等很长时间
删除单个列表、集合、有序集合或者哈希表类型的key,时间复杂度为O(M)。
如果元素数量过多的话,是会阻塞的
9、主从,哨兵,集群架构
主从模式
哨兵模式:
在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态,如果master节点异常,则会做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现一般,特别是在主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发,且单个主节点内存也不宜设置得过大,否则会导致持久化文件过大,影响数据恢复或主从同步的效率。所以主节点的内存不要超过10G
高可用集群架构:
redis集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。
10、Redis集群数据hash分片算法是怎么回事
Redis Cluster将所有数据划分为16384个slots(槽位),每个节点负责其中一部槽位,槽位的信息存储于
每个节点中。
当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。
这样当客户端要查找某个 key 时,可以根据槽位定位算法定位到目标节点。
槽位定位算法:HASH_SLOT=CRC16(key) mod 16384
再根据槽位值和redis节点的对应关系就可以定位到key具体是落在哪个节点上。
11、Redis执行命令竟然有死循环阻塞Bug
引起这种现象的原因是应为RANDOMKEY命令,该命令可以从Redis中随机取一个key。
- 由于Redis对于过期key的清理策略是定时删除和惰性删除结合,RANDOMKEY在随机拿出一个key,如果这个key是过期的话,就删除,然后再找一个没有过期的key。
- 如果过期key过多,RANDOMKEY命令就会阻塞
- 如果再slave上执行RANDOMKEY,由于slave节点的过期key要删除,必须master先清理然后master向slave发送一个DEL命令,告知slave删除这个key。所以RANDOMKEY命令不会删除过期key。这样就可能会寻找不到符合条件的key,进入死循环。
这是Redis的一个Bug,持续到5.0才被修复。现在RANDOMKEY循环100次没找到就退出循环。
12、一次线上事故,Redis主从切换导致了缓存雪崩
slave的机器时钟比master走很快,此时redis master里设置了过期时间的key,从slave角度来看,可能会有很多在master里没有过期的数据其实已经过期了。
如果此时,主从切换,把slave提升为新的master。它成为master之后,就会开始大量清理过期key,此时就会导致以下结果:
- master大量清理过期key,主线程可能会发生阻塞,无法及时处理客户端请求。
- Redis中数据大量过期,引发缓存雪崩。
当master与slave机器时钟严重不一致时,对业务的影响非常大。
13、Redis持久化RDB,AOF,混合持久化是怎么回事
RDB持久化:定的时间间隔内将内存中的数据集快照写入磁盘
在默认情况下,Redis将内存数据库快照保存在名字为dump.rdb的二进制文件中.
也可以对Redis进行设置.
save 60 1000 //60s内有至少1000个键被改动了,就自动保存一次数据集
save与 bgsave 比较
| 命令 | save | bgsave |
|---|---|---|
| IO类型 | 同步 | 异步 |
| 是否阻塞redis其他命令 | 是 | 否(在生成子进程执行调用fork函数十会有短暂阻塞) |
| 复杂度 | O(n) | O(n) |
| 优点 | 不会消耗额外内存 | 不会阻塞客户端命令 |
bgsave的写时复制机制
Redis借助操作系统提供的写时复制技术(Copy-On-Write, COW),在生成快照的同时,依然可以正常处理写命令. bgsave子进程是由主线程fork生成的, 可以共享主线程的所有内存数据. bgsave子线程运行后, 开始读取主线程的内存数据, 并把他们写入RDB文件. 此时,如果主线程对这些数据也都是读操作, 那么主线程和bgsave子线程互不印象. 但是,入宫主线程要修改一块数据,那么这块数据就会被复制一份,生成该数据的副本. 然后, bgsave子进程就会把这个副本数据写入RDB文件,而在这个过程中,主线程任然可以直接修改原来的数据.
AOF:所有修改的命令都会持久化到文件中
appendoly yes
appendfilename "appendonly.aof"
# 每秒持久化一次
appendfsync everysec
缓存雪崩、缓存穿透、缓存击穿
缓存穿透:缓存中查不到,数据库中也查不到。
如果有人一种用查不到的数据一直攻击服务器,服务器就会频繁查询数据库。导致服务器崩溃。
解决方案:
- 对参数进行合法校验
- 将数据库中没有查到的数据存到Redis中。(为了防止Redis被无用的Key占满一定要设置短的过期时间 )
- 引入bloom过滤器,(采用hash计算)来判断数据不在Redis中,不在就直接放回。
缓存击穿:缓存中没有,数据库中有。
一般出现在存储数据初始化以及key过期了的情况。
问题在于:重新写入缓存需要一定的时间,如果在高并发的场景下,过多请求就会瞬间写道DB上,给DB造成很大的压力。
解决方案:
- 设置热点信息永不过期。 这是要注意在value中包含一个过期时间,如何用一个协程来检查这些时间,过期了就需要重新到数据库中加载。
- 加载DB的时候,防止并发,互斥锁。
缓存雪崩:缓存大面积过期,导致请求都被转化到DB
解决方案:
- 把缓存的失效时间分散开。在本来的失效时间是+-一个随机值
- 互斥锁
redis的存储结构
所有的这些数都是存在一个hash表中
| 数据类型 | 可以存储 的值 | 存储结构 | 应用场景 |
|---|---|---|---|
| string | 字符串、整数或者浮点数 | 简单的动态字符串 | 做简单的键值对缓存 |
| list | 列表 | 压缩链表双向链表 | 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的 数据 |
| set | 无序集合 | hash表整数数组 | 交集、并集、差集的操作, 比如交集,可以 把两个人 的粉丝列表进行一个交集操作 |
| hash | 包含键值 对的无序 散列表 | hash表双向链表 | 结构化的数据,比如一个对象 |
| zset | 有序集合 | 压缩链表跳表 | 去重,可以排序, 如获取排名前几名的用户 |