目录
1.什么是Redis?
2.Redis和Memcached有什么区别?
3.为什么Redis作为MySQL的缓存?
4.Redis数据类型及其使用场景分别是什么?
5.五种常见数据类型是怎么实现的?
6.Redis是单线程吗?
7.Redis单线程模式是怎样的?
8.Redis采用单线程为什么还这么快?
9.Redis 6.0之前为什么采用单线程?
10.Redis 6.0之后为什么采用多线程?
11.Redis如何持久化?
12.AOF日志如何实现?
13.AOF日志为什么先执行命令,再将该命令写入到文件中?
14.AOF的缺点?
15.AOF的写回策略有哪些?
16.AOF日志过大,会触发什么机制?
17.重写AOF日志的过程是怎样的?
18.RDB快照是如何实现的?
19.RDB做快照时会阻塞线程吗?
20.为什么会有混合持久化?
1.什么是Redis?
Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此读写速度非常快,常用于缓存,消息队列、分布式锁等场景。
2.Redis和Memcached有什么区别?
1)Redis支持的数据类型多,比如:string、list、set、zset、hash等;Memcached只支持最简单的key-value数据类型。
2)Redis支持数据持久化,Memcached不支持持久化。
3)Redis支持发布订阅模型、lua脚本、事务等,Memcached不支持。
4)Redis原生支持集群模式,Memcached没有原生集群模式,需要靠客户端实现往集群中分片写入数据。
3.为什么Redis作为MySQL的缓存?
1)Redis具备高性能:用户第一次访问的MySQL数据会缓存在Redis中,下次访问时直接访问缓存。
2)Redis具备高并发:单台设备的Redis的QPS(Query Per Second,每秒钟处理完请求的次数)是MySQL的10倍,直接访问Redis能够承受的请求远大于MySQL。
4.Redis数据类型及其使用场景分别是什么?
常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合),随着 Redis 版本的更新,后面又支持了四种数据类型: BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)
数据类型 | 应用场景 |
string | 缓存对象、分布式锁等 |
list | 消息队列(生产者需自行实现全局消息唯一ID,不能以消费组形式消费数据)等 |
hash | 购物车、缓存对象等 |
set | 点赞、共同关注等 |
zset | 排行榜、电话等 |
bitmap | 签到、用户登录状态等 |
hyperloglog | 百万级网页UV计数等 |
GEO | 存储地理位置信息的场景,滴滴叫车等 |
stream | 消息队列(自动生成全局唯一消息ID,支持以消费组形式消费数据) |
5.五种常见数据类型是怎么实现的?
Tips:SDS(简单动态字符串)。SDS相比C语言的原生字符串:
SDS不仅可以保存文本数据,还可以保存二进制数据,SDS使用len属性的值而不是以空字符串判断字符串结束;
SDS使用len记录了字符串长度,获取字符串长度的时间复杂度是O(1),而C语言需要O(n);
Redis的SDS API是安全的,拼接字符串不会发生缓冲区溢出,因为底层会自动扩容。
6.Redis是单线程吗?
Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的,这也是我们常说 Redis 是单线程的原因。
但是,Redis不是单线程的,Redis启动的时候,会启动后台线程(BIO):
Redis 2.6版本之前,会启动处理关闭文件、AOF刷盘两个线程;
Redis 4.0版本之后,新增了一个新的后台进程,用来异步释放Redis内存,也就是lazyfree线程。例如执行 unlink key / flushdb async / flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。
后台线程相当于一个消费者,生产者把耗时任务丢到任务队列中,消费者(BIO)不停轮询这个队列,拿出任务就去执行对应的方法即可。
- BIO_CLOSE_FILE,关闭文件任务队列:当队列有任务后,后台线程会调用 close(fd) ,将文件关闭;
- BIO_AOF_FSYNC,AOF刷盘任务队列:当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到队列中。当发现队列有任务后,后台线程会调用 fsync(fd),将 AOF 文件刷盘,
- BIO_LAZY_FREE,lazy free 任务队列:当队列有任务后,后台线程会 free(obj) 释放对象 / free(dict) 删除数据库所有对象 / free(skiplist) 释放跳表对象;
7.Redis单线程模式是怎样的?
Redis 6.0版本之前的单线程模式如下:
Redis初始化时,创建epoll对象,调用socket()、bind()、listen()创建服务端并监听,然后将监听socket加入到epoll()中,同时注册连接事件处理函数。
Redis初始化后,主线程进入事件循环,主要做以下事情:
1)首先调用处理发送队列的函数,判断发送队列是否有任务,如果有,就调用write函数将客户端缓存里的数据发送出去,如果没有发送完,就注册写事件处理函数,等待epoll_wait发现可写后处理。
2)接着调用epoll_wait等待事件到来;
i)连接事件到来,调用连接事件处理函数,调用accept获取已连接的socket -> 将socket加入到epoll中 -> 注册读事件处理函数;
ii)读事件到来,调用读事件处理函数,调用read处理客户端发来的数据 -> 解析命令 -> 处理命令 -> 将客户端对象添加到发送队列 -> 将执行结果写到发送缓存区等待发送;
iii)写事件到来,调用写事件处理函数,通过write将客户端缓存区的数据发送出去,如果没有发送完,则会注册写事件处理函数,等待epoll_wait发现可写后处理。
8.Redis采用单线程为什么还这么快?
1)大部分操作都在内存中完成;
2)单线程模式避免了多线程之间的竞争;
3)采用了I/O多路复用处理大量请求。
9.Redis 6.0之前为什么采用单线程?
单线程可维护性高,多线程会导致一系列问题,如增加系统复杂性、存在线程切换、加锁解锁、死锁造成的性能损耗。
10.Redis 6.0之后为什么采用多线程?
Redis的主要工作是网络I/O和执行命令,随着网络硬件性能的提升,Redis在网络I/O的处理上会出现性能瓶颈。为了提高网络I/O的并行度,所以对网络I/O采用多线程来处理。但是对于命令的执行,还是采用单线程。
默认情况下,I/O多线程用来处理写数据,不用来处理读数据。
Redis 6.0版本后,默认会额外启动6个线程:
- Redis-server : Redis的主线程,主要负责执行命令;
- bio_close_file、bio_aof_fsync、bio_lazy_free:三个后台线程,分别异步处理关闭文件任务、AOF刷盘任务、释放内存任务;
- io_thd_1、io_thd_2、io_thd_3:三个 I/O 线程,io-threads 默认是 4 ,所以会启动 3(4-1)个 I/O 多线程,用来分担 Redis 网络 I/O 的压力
11.Redis如何持久化?
1)AOF日志:每执行一条写命令,就把该命令以追加的方式写入到一个文件中;
2)RDB快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
3)混合持久化:AOF和RDB混合的方式。
12.AOF日志如何实现?
Redis在执行完一条写命令操作后,就将该命令以追加的方式写入一个文件中,Redis重启后,会先读取该文件里的命令,然后逐一执行,恢复数据。
13.AOF日志为什么先执行命令,再将该命令写入到文件中?
1)避免该命令的额外的检查开销:防止语法错误的命令写入到日志文件中,在恢复数据时会出错。
2)不会阻塞当前写操作的执行。
14.AOF的缺点?
1)数据可能会丢失:执行命令和将命令写入日志是两个过程,可能还没有将命令写入日志文件中,服务器就发生了宕机,写命令没有写入到文件中,造成数据丢失。
2)可能会阻塞其他操作:因为AOF日志是在主线程中执行的,所以当Redis把日志文件写入磁盘的时候,会阻塞后续的操作。
15.AOF的写回策略有哪些?
AOF写入日志的过程:
Always:每次写操作完成后,都将AOF日志数据写入到磁盘。
Everysec:每次写操作完成后,先将命令写入到AOF的内核缓存区,每隔一秒将缓存区的数据写到磁盘里。
No:不由Redis控制写回磁盘的时间。每次写操作完成后,先将命令写入到AOF的内核缓存区,由操作系统决定什么时候将数据写到磁盘。
16.AOF日志过大,会触发什么机制?
当AOF日志文件大小超过设定的阈值后,触发AOF重写机制,来压缩AOF文件。
AOF重写机制:读取当前数据库中的所有键值对,每个键值对都用一条命令记录到新的AOF文件中,全部键值对记录完成后,会用新的AOF文件替换掉现有的AOF文件。
17.重写AOF日志的过程是怎样的?
Redis的重写AOF的过程是由后台子进程bgrewriteaof完成的。
触发重写机制后,会创建子进程bgrewriteaof,该子进程会读取当前数据库中的所有键值对,每个键值对都用一条命令记录到新的AOF文件中。
在重写过程中,主进程可以正常处理命令。
Tips:
i)为什么是子进程来完成重写AOF日志,而不是子线程?
子进程重写AOF文件,避免阻塞主进程处理命令;
如果是子线程来处理重写AOF,子线程和主线程共享内存数据,当修改内存数据的时候需要加锁来保证数据安全,降低性能。而使用子进程,虽然父子进程共享内存数据,但是子进程对该共享区域的数据只能可读,当主进程对内存执行写操作时,会进行写时复制,于是父子进程各自拥有独立的数据副本,不需要加锁来保证数据安全。
ii)如果在重写过程中,主进程执行写命令更改一个已存在的key-value,会触发写时复制,那么该key-value在子进程和主进程中的内存数据不一致了,这时要怎么办?
为解决这种数据不一致问题,Redis设置了一个AOF重写缓冲区,在bgrewriteaof执行重写AOF的过程中,如果主进程进行了写操作,那么就会将写命令同时写入AOF缓冲区和AOF重写缓冲区。
当子进程完成重写工作后,会给主进程发送一个信号,主进程收到信号后,调用一个信号处理函数,该函数的作用是:将AOF重写缓存区中的内容追加到新的AOF文件中,保证数据一致性;新的AOF进行改名,覆盖现有的AOF文件。
18.RDB快照是如何实现的?
记录某一时刻的所有内存数据到RDB文件中,恢复数据时,将RDB文件中的数据写入到内存。
19.RDB做快照时会阻塞线程吗?
Redis提供save和bgsave来生成RDB文件。
save:在主线程中执行该命令,生成RDB文件,如果RDB文件太大,会阻塞主线程;
bgsave:会创建一个子进程来生成RDB,不会阻塞主进程。
20.为什么会有混合持久化?
RDB优点是恢复数据快,但是快照的频率太低会丢失大量数据,频率太高会影响性能;AOF文件优点是丢失数据少,但是恢复数据慢。为了集合两者的优点,所以有了混合持久化。
混合持久化的过程:
混合持久化工作在AOF日志重写的过程,当开启了混合持久化,重写AOF日志时,fork出来的子进程会先将与主进程共享的数据以RDB的方式写入到新的AOF文件中,然后主线程的操作命令会被记录到重写缓冲区中,重写缓冲区里的增量命令会以AOF方式写入到AOF文件中,写入完成后通知主进程将含有RDB格式和AOF格式的AOF文件替换旧的AOF文件。
使用了混合持久化,AOF文件里的前半部分是RDB格式的全量数据,后半部分是AOF格式的增量命令。