原理1:鞭辟入里--线程IO模型
- Redis是个单线程程序。Redis的所有数据都在内存中,所有的运算都是内存级别的运算。对于一些时间复杂度为O(n)级别的指令,需谨慎使用,一不小心就会造成redis卡顿
- Redis单线程处理并发客户端连接的技术被称为多路复用,使用select系列的事件轮询
- select函数是操作系统提供给用户程序的。
- Redis会将每个客户端套接字都关联一个指令队列,客户端的指令通过队列来排队顺序执行
- Redis同样为每个客户端套接字关联一个响应队列
- Redis的定时任务会记录在最小堆的数据结构中
原理2:交头接耳--通信协议
- RESP是redis序列化协议的简写,优势:实现简单、解析性能好
127.0.0.1:6379> set author codehole
*3
$3
set
$6
author
$8
codehole
127.0.0.1:6379> scan 0
1) "0"
2) 1) "info"
2) "books"
3) "author"
*2
$1
0
*3
$4
info
$5
books
$6
author
原理3:未雨绸缪--持久化
- Redis的持久化有两种,快照和AOF日志。快照是一次全量备份,AOF日志是连续的增量备份
- 快照是内存数据的二进制序列化形式,在存储上非常紧凑,而AOF日志记录的是内存数据修改的指令记录文本。Redis重启时需要加载AOF日志进行指令重放,过程漫长,所以需要定期进行AOF重写
- Redis为了边持久化边响应客户端请求,Redis使用操作系统的多进程COW(copy on write)。
- Redis在持久化时会调用glibc的函数fork产生一个子进程来处理快照持久化,与父进程共享内存里的代码段和数据段
- 子进程只是对数据进行遍历然后序列化写道磁盘,父进程需要根据客户端请求对内存数据进行修改。这时候就会使用操作系统的COW机制来进行数据段页面的分离。当父进程对其中一个系统页面数据进行修改时,会将被共享的页面复制一份并进行修改。
- AOF日志存储的是Redis服务器的顺序指令序列,只记录对内存进行修改的指令记录。
- Redis收到客户端修改指令后,先进行参数校验,没有问题就立即将改指令文本存储到AOF日志中,然后再执行指令。
- Redis提供了bgrewriteaof指令对AOF日志进行重写瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列Redis的操作指令,序列化到一个新的AOF日志文件中,序列化完成后再将操作期间发生的增量AOF日志追加到新的AOF日志中。追加完成后立即替代旧的AOF日志文件
- 当程序对文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓冲中,然后内核会异步将数据刷回磁盘。这时候机器宕机,AOF日志内容可能还没有来得及刷到磁盘就会出现日志丢失。
- Linux的glibc提供fsync(int fd)函数可以将指定文件的内容强制从内核缓冲刷到磁盘。因为fsync时磁盘IO操作,所以一般生产环境中通常每个1s执行一次fsync操作,周期1s是可配置的。在数据安全性和性能中做一个折中。
- 因为持久化是耗时的IO操作,会加重系统负载,降低redis性能,增加系统IO负担。所以一般主节点不做持久化,由从节点进行持久化操作。
- Redis4.0为了解决rdb丢失大量数据和aof性能慢的两个问题,采用了新的持久化方案--混合持久化。将rdb文件的内容和增量aof日志文件存在一起。redis重启时,先加载rdb内容再重放增量aof日志,重启效率因此大幅得到提升。
原理4:雷厉风行--管道
- Redis管道Pipeline本质上是由客户端提供的,跟服务器没什么直接关系。
- 管道的本质就是Redis客户端改变了消息的读写顺序。redis的write只负责将数据写到本地操作系统内核的发送缓冲旧返回。read只负责将数据从本地操作系统内核的缓冲中取出来。对于管道来说,连续的write操作根本没有耗时,之后第一个read操作会等待一个网络的来回开销,然后所有的响应消息就都会送到内核的读缓冲了。后续的read操作可以从缓冲拿到结果。
原理5:同舟共济--事务
- Redis事务具有简单性,能够确保多个操作的原子性,但事务模型不严格。
- Redis事务的关键字:multi/exec/discard
- Redis事务中的所有指令再exec之前不执行,而是缓冲再服务器的一个事务队列中,一旦收到exec指令,才开始执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。
- Redis事务根本不能算"原子性",仅仅满足事务的隔离性。事务在遇到指令执行失败后,后面的指令还继续执行。
- 事务一般会结合pipeline一起使用,将多次IO操作压缩为单次IO操作。
- watch机制相当于乐观锁。在事务开始之前watch一个或多个关键变量,当事务执行时,Redis会检查关键变量自watch之后是否被修改,如果被修改,则exec指令返回null,告知客户端事务执行失败。
原理6:小道消息--PubSub
- 为了支持消息多播,Redis单独使用了PubSub来支持消息多播,也就是发布订阅者模型。
- 客户端发起订阅命令后,Redis会立即给予一个反馈消息通知订阅成功。
- PubSub在生产者传递消息的时候会直接找到相应的消费者传递过去。如果没有消费者,消息直接丢弃。如果消费者中间挂掉,则断连期间的消息就彻底丢失。
原理7:开源节流--小对象压缩
- 如果Redis内部管理的集合数据结构很小,它会使用紧凑存储形式压缩存储。
- Redis的ziplist是一个紧凑的字节数组结构。每个元素之间都是紧挨着的。
- Redis的intset是一个紧凑的整数数组结构,用于存放元素都是整数且元素个数较少的set集合。如果整元素可以用用uint16表示,那么intset的元素就是16位的数组,如果新加入元素超过uint16的范围,那么就是用uint32,如果超时uint32就是用uint64。如果添加元素为字符串,则立即升级为hashtable结构。
- 当集合对象的元素不断增加,或者某个value值过大,小对象存储也会被升级为标准结构。
- Redis并不总是可以将空闲内存立即归还给操作系统。操作系统回收内存是以页为单位的,如果这个页上有一个key还在使用,就不能被回收。内存虽然不会被回收,但会被重用。
- Redis的内存分配细节由第三方内存分配库实现。jemalloc或者tcmalloc,jemalloc性能稍好一点,也是Redis的默认内存分配器
原理8:有备无患--主从同步
- CAP 一致性、可用性、分区容错性。网络分区发生时,一致性和可用性两难全。
- Redis的主从数据是异步同步的,所以分布式的Redis系统并不满足"一致性"要求。在主从网络断开情况下,主节点依旧可以提供服务,所以是满足"可用性"的。
- Redis保证"最终一致性",从节点会努力追赶主节点,最终从节点状态与主节点状态将保持一致。
- Redis的主从同步细分为主从和从从同步。Redis同步的是指令流,将产生修改的指令记录在本地的内存buffer中,然后异步将buffer中的指令同步到从节点。内存buffer是一个定长的环形数组。数组内容满了会从头开始覆盖
- 如果从节点网络不好没有同步的指令被覆盖,这时候需要用到更加复杂的同步机制--快照同步
- 快照同步:在主库上执行一次bgsave将内存数据快照到磁盘文件,然后将快照文件内容全部传送到从节点,从节点执行一次全量加载,加载完毕通知主节点继续进行增量同步
- 如果快照同步时间过长,指令同步的buffer又被覆盖,则同步失败,再次发起快照同步。所以需要配置合适的复制buffer大小,避免快照复制的死循环
- 增加从节点会首先进行一次快照同步,然后进行增量同步
- 无盘复制:主节点直接通过套接字将快照内容发送到从节点,从节点将接受的内容存储到磁盘文件再进行一次性加载