Redis为什么快
- redis是基于内存的数据库,相比于基于磁盘的数据库效率要高很多
- redis有经过优化的高效的数据结构,而且支持多种数据结构,每种数据结构对应不一样的编码
- redis的工作线程是单线程的,避免了线程之间的切换,但是对于时间复杂度为O(n)的命令,需要谨慎使用,很有可能会造成卡顿
- i/o是多线程的,另外如持久化、异步删除、集群数据同步等等,实际是由额外的线程执行的。
- 采用了虚拟内存机制,就是暂时把不经常访问的数据从内存交换到磁盘中,可以实现冷热数据分离,使热数据仍在内存中、冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。
非阻塞IO
- 非阻塞IO在套接字对象上提供了一个Non Blocking(非阻塞性)选项,开启后读写方法不会受到阻塞,而是能读多少就读多少,能写多少就写多少,这样线程在读写的时候就不需要阻塞了,读写可以瞬间完,然后线程可以去做其他任务
- 读多少取决去内核为套接字分配的读缓冲区大小,写取决于内核为套接字分配的写缓冲区的大小,读写都会通过返回值告知程序实际读写了多少字节
Redis非阻塞io多路复用线程模型
-
Redis工作线程是单线程,持久化和删除过期键使用的是另外的线程
-
关于单线程如何高效的处理并发的网络请求,redis使用的是基于react模式(反应器模式,当检测到一个新的事件,将其发送给相应的Handler去处理)开发的网络事件处理器被称为文件事件处理器。
-
组成结构:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。
-
Redis客户端对服务端的调用分为发送命令,执行命令,返回结果三个过程
- 文件事件处理器使用I/O多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器
- 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件
- 尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生事件的套接字都推到一个队列里面,然后通过这个队列,以有序、同步、每次一个套接字的方式向文件事件分派器传送套接字:当上一个套接字产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕), I/O多路复用程序才会继续向文件事件分派器传送下一个套接字。
- 多路复用IO的实现是通过包装select、epoll、evport和kqueue这些I/O多路复用函数库来实现的,每个I/O多路复用函数库在Redis源码中都对应一个单独的文件,比如ae_select.c、ae_epoll.c、ae_kqueue.c等,Redis为每个I/O多路复用函数库都实现了相同的API,所以I/O多路复用程序的底层实现是可以互换的
- 文件事件的类型分为可读事件(connect read close)和可写事件(write),如果一个套接字同时产生了这两种事件,那么服务器将先读套接字,后写套接字。
- Redis为文件事件编写了多个处理器,分别为socket关联连接、命令请求、命令回复处理器等。
redis通信协议
- Redis 客户端 - 服务端通信协议称之为 RESP 协议(Redis Serialization Protocol),也就是redis的序列化协议
- RESP 协议只用于 客户端 - 服务端 之间的交流,redis cluster各节点之间采用不同的二进制协议(采用 Gossip 协议)进行交流。
- 在 RESP 协议中,分为五种最小单元类型,第一个字节决定了具体数据类型,单元结束时会统一加上回车换行符 \r\n
- 简单字符串:Simple Strings,第一个字节响应 +
- 错误:Errors,第一个字节响应 -
- 整型:Integers,第一个字节响应 :
- 批量字符串:Bulk Strings,第一个字节响应 $
- 数组:Arrays,第一个字节响应 *
- null会用多行字符串表示,只不过长度写为-1;空串也用多行字符串表示,长度为0
- RESP 协议有大量冗余的回车换行符,但是依然很受欢迎,因为实现简单且容易理解
Redis 的持久化机制有哪些
-
RDB
- 就是把内存数据以快照的形式保存到磁盘上。
- redis采用了操作系统的多进程COW(copy on write)机制来实现快照持久化:
- 在进行RDB持久化时会产生一个子进程,由子进程进行持久化,父进程继续处理客户端请求
- 子进程刚刚产生时,和父进程共享内存中的数据,这是linux操作系统的机制,为了节约内存资源,尽可能的让他们共享起来,所以在进程分离的时候,内存的增长基本没有变化
- 子进程做数据持久化,不会修改现有的内存结构,只是对数据结构遍历读取,然后写到磁盘,这时会使用操作系统的COW机制来进行数据段页面的分离
- 当父进程对其中的一个页面进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页进行修改,这样对子进程的数据就不会造成影响,这也是为什么叫做快照的原因,数组在分离的一瞬间就不会在改变了
- 随着父进程修改操作的持续进行,越来越多的页面被分离出来,内存也会持续增长,但是不会超过原有数据的2倍
- RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个
dump.rdb
文件,Redis 重启的时候,通过加载dump.rdb
文件来恢复数据。 - 适合大规模的数据恢复场景,如备份,全量复制等
- 但是没办法做到实时持久化/秒级持久化,新老版本存在RDB格式兼容问题
-
AOF
-
redis收到客户端修改的指令后,进行参数校验、逻辑处理,如果没有问题,就将改指令存到AOF日志
-
AOF(append only file) 持久化,采用日志的形式来记录每个写操作,追加到文件中,重启时再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。默认是不开启的。
-
数据的一致性和完整性更高
-
AOF日志在长期运行过程中会变得很大,文件越大,数据恢复也就越慢,所以要定期给AOF文件瘦身
- redis提供了bgrewriteaof指令对AOF日志进行瘦身,原理是开辟一个子进程对内存进行遍历,转化成一系列的redis操作指令,序列化到新的AOF文件
- 序列化完毕后再把序列化期间产生的增量AOF日志追加到新的AOF文件中,追加完毕后,就可以代替旧的AOF文件了
-
-
混合持久化
- redis4.0,将RDB文件的内容和增量的AOF日志文件存在一起,AOF日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量日志
- 再重启的时候,先加载RDB的内容,然后再重放增量的AOF日志,使重启效率得到大幅提升
手工命令的方式触发RDB生成快照文件
-
save 命令
- save 命令是同步方式生成快照,会造成Redis阻塞,所有后续到达的命令要等待save完成以后才能执行。
-
另一种是 bgsave 命令,
- bgsave 命令采用异步方式生成快照,Redis会fork出一个子进程进行RDB文件的生成。
- Redis只有在fork子进程时被阻塞,子进程完成快照生成的同时,Redis可以正常工作。
bgsave 自动触发机制
-
自动触发最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。
-
在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点;
-
执行shutdown命令时,自动执行rdb持久化
AOF写磁盘:
- AOF日志是以文件形式存在的,当程序对AOF文件进行写操作的时候,实际上是把内容写到了内核的缓存中,如果此时宕机是会丢失数据的
- 所以redis提供了 fsync(int fd) 函数可以指定文件的内容强制从内核刷到磁盘,只要进程实时调用这个函数,就可以保证数据不丢失,但是性能会很差,所以通常都是每隔一秒(可配置)执行一次fsync操作
- 此外redis还提供了另外两种策略,一种是永不调用fsync,一种是一个指令就调用一次fsync,这两种都不可取
Redis为什么先执行指令,再写AOF日志
- 在常见的数据库中,持久化重做日志一般是先写日志再修改数据库,保证数据/操作不会丢失。但是redis是先执行指令,再写AOF日志
- 首先是避免额外检测的开销:如果先将写操作命令记录到 AOF 日志里,再执行该命令的话,如果当前的命令语法有问题,那么如果不进行命令语法检查,该错误的命令记录到 AOF 日志里后,Redis 在使用日志恢复数据时,就可能会出错
- 其次是当写操作命令执行成功后,才会将命令记录到 AOF 日志,不会阻塞当前写操作命令的执行
AOF 后台重写会阻塞主进程吗
- 在整个 AOF 后台重写过程中,发生写时复制会对主进程造成阻塞、信号处理函数执行时也会对主进程造成阻塞。
- 其他时候,AOF 后台重写都不会阻塞主进程
redis的管道:
- 客户端通过改变管道中的指令列表读写顺序,就可以节省IO时间,管道中的指令越多,效果越好
redis的事务:
-
redis的每个事务的操作指令都有multi、exec 和 discard,multi代表事务开始,exec 代表事务的执行,discard是指丢弃事务缓存队列中的所有指令
-
所有指令再exec 之前不执行,而是缓存再服务器的事务队列中,收到exec指令才开始执行,执行完毕后一次返回所有指令的运行结果
-
单个redis的命令是原子性的,但是redis的事务是没有原子性的,事务就是一组命令的集合,可以理解为批量执行脚本,
-
redis事务执行失败,有两种情况,语法错误或者数据结构类型错误,如果语法错误,那么所有的事务都会执行失败,如果是类型错误,只有错误的那条事务会执行失败,不会导致前面的指令回滚,也不会造成后续指令不执行
watch:
- redis提供了watch机制,是一种乐观锁
- watch再事务开始之前盯住一个或者多个变量,服务器收到exec执行事务时,会检查变量自watch之后是否被修改,如果被修改,exec执行就会返回null,告知客户端事务执行失败
- redis禁止再multi和exec之间使用watch,必须在multi之前使用watch
redis消息多播
- redis单独使用了一个模块来支持消息多播,模块的名字叫做PubSub,被5.0的Stream取代
小对象压缩:
- 如果redis内存占用不超过4GB,可以考虑使用32bit进行编译,能够节约大量内存
- 如果redis内部管理的集合数据结构很小,就会使用紧凑存储形式压缩存储,例如压缩链表ziplist、intset
redis的内存回收机制:
- redis并不总是将空闲内存立即归还给操作系统,因为操作系统是以页为单位回收内存的,只要这个页上还有一个key在使用,就不应该被回收
- 虽然无法立即回收已经删除的key的内存,但是也可以重新使用这些未被回收的空闲内存
- 当然可以执行flushdb执行强制回收
redis内存分配算法:
- redis的内存分配算法采用第三方内存分配库区实现