Redis原理解析
Redis是单线程程序。单线程的Redis为何还能这么快?
1、所有的数据都在内存中,所有的运算都是内存级别的运算(因此时间复杂度为O(n)的指令要谨慎使用)
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。2、多路复用,NIO来处理客户端的并发连接
非阻塞IO,Non-block IO, NIO,非阻塞模式,使一个线程从某通道发送请求数据读取数据,如果目前没有数据可读时,就什么都
不会获取,而不是保持线程阻塞,直到有数据可读之前,该线程可以继续做别的事情,非阻塞写也是如此,能写多少取决
于内核为套接字分配的写缓冲区的空闲字节数,不必等到完全写入这个线程可以去做别的事情。线程通常将非阻塞IO的空
闲时间用于在其他通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
阻塞IO,BIO,如Java IO中的各种流都是BIO,阻塞的。当一个线程调用read或者write方法时,该线程会被阻塞,知道有一些数据
被读取,或数据完全写入。该线程在此期间不能再干别的事情了。
事件轮询(多路复用)
非阻塞IO有个问题,那就是线程要读数据,结果读了一部分后返回了,那么当数据到来时,如何通知线程继续读呢?写也是一
样,如果缓冲区写满了没有写完,剩下的数据何时继续写,线程也应该得到通知。
事件轮询API就是用来解决这个问题的。最简单的时间轮询API是select函数。它是操作系统提供给用户程序的API。输入是读
写描述符列表,read_fds&write_fds,输出是与之对应的可读可写事件。同时还提供了一个timeout参数,如果没有任务事件到来,
那么就最多等待timeout的值的时间,线程处于阻塞状态。一旦期间没有任务事件到来,就可以立即返回。时间过了之后没有任务
事件到来,也会立即返回。拿到事件后,线程就可以挨个处理相应的事件。处理完了继续过来轮询。于是线程就进入了一个死循环
,这个死循环称为事件循环,一个循环为一个周期。
通过select系统调用同时处理多个通道描述符的读写事件,因此将这类调用称为多路复用API。
指令队列:Redis会将每个客户端套接字都关联一个指令队列。客户端的指令通过队列来排队进行顺序处理,先到先服务。
响应队列:Redis会为每个客户端套接字都关联一个响应队列。Redis服务器通过响应队列来将指令的返回结果回复给客户端。如果队列
为空,那么意味着连接暂时处于空闲状态,不需要去获取写事件。
定时任务:服务器除了要响应IO事件外,还要处理其他的事情。比如定时任务就是非常重要的一件事。如果线程阻塞在select系统调用上
,定时任务无法得到准时调度。Redis的定时任务会记录在一个被称为最小堆的数据结构中。在这个堆中,最快要执行的任务排
在堆的最上方,每个循环周期里。Redis都会对最小堆里面已经到时间点的任务进行处理。处理完毕后,将最快要执行的任务还
需要的时间记录下来,这个时间就是select系统地哦暗涌的timeout参数。因为Redis知道未来timeout的值的时间内,没有其他定
时任务需要处理,所以可以安心睡眠timeout的值的时间。
持久化
Redis的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证Redis中的数据不会因为故障而丢失,这
种机制就是Redis的持久化机制。
Redis的持久化机制有两种:
一、快照RDB
1、一次全量备份,使用 BGSAVE命令
2、保存方式是内存数据的二进制序列化形式,在存储上非常紧凑
3、使用操作系统的多进程COW(copy on write)机制来实现持久化,持久化时调用glibc的函数fork(分岔)产生一个子进程,持久
化完全交给子进程来处理,父进程继续处理客户端请求
4、COW机制的数据页面的分离。父进程在对页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对复制出来的页面
进行修改。这时子进程的页面是没有变化的,还是进程产生那一瞬间的数据,所以这种持久化叫做快照的原因。
5、使用fork子进程,无法实时,宕机会造成数据丢失
二、AOF日志
1、连续的增量备份,使用appendonly yes开启
2、存储的是Redis服务器的顺序指令序列,只记录对内存进行修改的指令序列
3、记录的是内存数据修改的指令记录文本,在长期的运行过程中会变得非常庞大,数据库重启时需要加载AOF日志进行指令重放
,比较耗时,所以需要定期进行AOF重写,给AOF日志瘦身
4、Redis收到客户端修改指令后,进行参数校验、逻辑处理,如果没问题,就立即将该指令写到缓冲区中,然后每秒钟调用一次
fsync将指令存储到AOF日志中
5、使用bgrewriteaof指令对AOF日志进行瘦身,即开辟一个子进程对内存进行遍历,转换成一系列Redis的操作指令,序列化到一
个新的AOF日志文件中,再将操作期间新增的AOF日志追加到这个新的AOF日志文件中,替代旧的AOF日志文件,完成瘦身。
Redis4.0混合持久化
实际应用中重启Redis时,很少使用RDB来恢复内存状态,因为会丢失大量数据。所以我们通常使用AOF日志重放,但是重放AOF日志
相对于RDB要慢得多。
混合持久化:将RDB文件的内容和增量的AOF日志文件存在一起。这里的AOF日志不是全量的日志而是自持久化开始到持久化结束的这
段时间发生的增量AOF日志,通常这部分AOF日志很小。因此重启的时候先加在RDB内容,然后再重放增量AOF日志,替
代之前的AOF的全量文件重放,重启效率得到大幅度提升。
事务
Redis的事务模型并不严格
基本的事务操作都有begin、commit和rollback。begin指示事务的开始,commit指示事务的提交,rollback指示事务的回滚。
Redis事务的指令也差不多,分别是multi、exec、discard。multi指示事务的开始,exec指示事务的执行,discard指示事务的丢弃。
Redis的指令在exec之前不执行,而是缓存在服务器的事务队列中,服务器一旦收到exec指令,才开始执行整个事务。因为Redis是单
线程,所以在执行队列中的命令时不会被其他指令打搅。
但是Redis的事务不具备原子性,而仅仅满足了事务的隔离性中的串行化,当前事务执行不会被其他事务干扰。
优化:Redis事务在每发送一个指令到事务缓存队列都要经过一次网络读写,当一个事务内部的指令较多时,需要的网络IO也会线性增长,
所以通常Redis的客户端在执行事务时都会结合pipeline一起使用。
Watch:
多个客户端并发修改Redis中的一条记录。需要先读,再写。为了保证线程安全,一种方式是通过Redis分布式锁的方式,但是Redis
分布式锁是悲观锁。Redis提供了watch机制,是一种乐观锁在multi之前监视某个关键变量,若在watch之后被修改了(包含当前事务
所在的客户端),如果关键字
