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格式的**增量命令**。

混合持久化的AOF文件内容
版权归原作者 Whale_XH 所有, 如有侵权,请联系我们删除。