第1章 引言
第2章 简单动态字符串
2.1 SDS是什么?
redis只会使用C字符串作为字面量,大多数情况下,用SDS(simple dynamic string)作为字符串表示。
2.2 SDS的数据结构?
struct sdshdr {
int free; //buf数组中未使用的字节数量
int len; //buf数组中已使用的字节数量,等于字符串的长度
char buf[]; //字节数组,字节数组的长度 = len + 1 + free
}
2.3 SDS与C字符串比较的优势?
- 常数复杂度获取字符串长度(直接读取len属性);
- 杜绝缓冲区溢出;
- 减少字符串长度修改时内存重新分配的次数;
- 二进制安全;
- 兼容部分C字符串函数。
2.4 SDS扩展分配策略?
在扩展SDS前,先检查free属性是否足够,如果足够,则直接使用。扩展SDS时,如果扩展后的len属性值小于1MB,则分配和len属性值相等的free;如果大于等于1MB,则只分配1MB的free属性值。
2.5 SDS惰性空间释放?
当SDS字符串缩短时,程序不会立即回收多出来的字节,而是先把多出来的字节记录在free属性。(可以通过SDS API执行重分配,释放多出的字节)。
第3章 链表
3.1 redis链表?
由于redis使用的自己实现的双端无环链表,每个链表节点都有prev、next的指针,头节点的prev和尾节点的next指向Null。
第4章 字典
4.1 redis的字典是什么?
字典也称为映射(map),是用于保存键值对的抽象数据结构,redis字典底层用哈希表(数组 + 链表)实现。
4.2 哈希表解决哈希冲突的方式?
redis哈希表使用链地址法解决冲突,被分配到同一个索引位上的键值对采用头插法连接成一条链表。
4.3 渐进式rehash是什么?
在对字典进行扩展或者收缩操作时,程序需要将原有的字典rehash到新的字典里。在渐进式rehash过程中,字典同时持有ht[0](旧表)、ht[1](新表)两个哈希表,只有当程序对key进行操作时,才会将ht[0]
的键值对更新到ht[1],这样ht[0]只减不增,最终ht[0]变成空表,完成rehash。
第5章 跳跃表
5.1 redis跳跃表是什么?
跳跃表是redis有序列表的底层实现之一,跳跃表节点按照各自的score从小到大排序。
5.2 redis跳跃表的实现?
redis跳跃表由zskiplist(header、tail、length)和zskiplistnode(backward、score、obj、level[])实现。level包含前进指针(forward)和跨度。
5.3 跳跃表的层高?
每个跳跃表节点的层高都是1~32间的随机数。
第6章 整数集合
6.1 整数集合是什么?
整数集合是redis集合键(set)的底层实现之一。
6.2 整数集合的底层实现?
整数集合的底层实现是intset.contents数组,该数组以有序(从小到大)、无重复的方式保存集合元素,并且在需要时能自动改变集合的数据类型。intset.contens数组的数据类型实际上由intset.encoding控制。
6.3 整数集合的升级与降级?
- 当向整数集合中添加元素时,如果新元素的数据类型比intset.encoding大,程序会优先对整数集合进行升级(将所有的元素都升级为较大的数据类型),再添加新元素。
- 整数集合只支持升级,不支持降级。
6.4 整数集合升级的作用?
节约内存。
第7章 压缩列表
7.1 压缩列表是什么?
压缩列表被用作列表键和哈希键的底层实现之一。压缩列表可以包含任意多个节点,每个节点都可以保存一个字节数组或者整数。
7.2 压缩列表如何实现从后往前遍历?
压缩列表的previous_entry_length属性记录上一个节点的长度,再根据当前节点的位置就能计算出上一个节点的起始位置,从而实现从尾部往头部遍历。
7.3 连锁更新是什么?
如果前一个节点的长度小于254,previous_entry_length则用1字节空间保存它的长度;如果前一个节点的长度大于等于254,previous_entry_length则用5字节空间保存它的长度。若压缩列表中有连续多个长度介于250到253间的节点,当向压缩列表的头部插入一个大于254字节的节点时,就可能会引发大面积的空间重分配。
第8章 对象
8.1 redis对象是什么?
redis基于数据结构(int、embstr、raw、ziplist、hashtable、linkedlist、inset、skiplist)创建了对象系统,使用对象保存键和值。
8.2 redisObject的构造函数?
struct redisObject{
type; //对象类型(string、hash、list、set、zset)
encoding; //对象使用的哪种数据结构作为底层实现
*ptr; //指向底层数据结构的指针
lru; //对象最后一次被命令程序访问的时间,可以用于计算对象的空转时间
}
8.3 字符串对象的底层实现?
- int:字符串是long以内的整数值;
- embstr:字符串长度小于40字节;
- raw(SDS):其它。
8.4 哈希对象的底层实现?
- ziplist:键和值的字符串长度都小于64字节,且键值对数量小于512个;(ziplist保存多对紧挨着的键值节点,键节点在前,值节点在后)
- hashtable:其它。
8.5 列表对象的底层实现?
- ziplist:字符串长度小于64字节,且列表元素数量小于512个;
- linkedlist:其它。
8.6 集合对象的底层实现?
- intset:所有元素都是整数,且集合元素数量小于512个;
- hashtable:其它。(键是字符串对象,值是NULL)。
8.7 有序集合对象的底层实现?
- ziplist:字符串长度小于64字节,且集合元素个数小于128个;
- skiplist:其它。
8.8 多态命令是什么?
服务器在执行某些命令前,会先检查键的值对象能否执行指定的命令,再根据将的值对象所使用的编码(encoding)来选择命令的实现。
8.9 redis内存回收机制?
redis对象系统使用引用计数法来实现内存回收机制。
8.10 redis对象共享?
共享对象的值越复杂,验证共享对象与目标对象是否相同的复杂度越高。因此,redis默认只对整数值(0到9999)的字符串对象进行共享。
第9章 数据库
9.1 redis服务器的组成?
redis服务器所有数据库都保存在redisServer.db数组中,数据库数量由redisServer.dbnum决定。
9.2 redis客户端的组成?
redis客户端默认指向索引0数据库,可以通过select命令切换。客户端修改redisClient.db属性来修改目标数据库指针。
9.3 redis数据库的组成?
redis数据库主要由dict和expires两个字典构成,dict称为键空间字典,key是键对象,value是string、hash、list、set、zset;expires称为过期字典,key是键对象(与dict指向同一个键对象),value是long类型时间戳。
9.4 redis过期删除策略?
- 定时删除:定时器定时清空过期的键,CPU换内存;
- 惰性删除:从键空间获取键值时进行过期检查,内存换CPU;
- 定期删除:限制删除操作的时长及频率,每次对部分数据库中的键进行抽样检查,如果键过期则删除,且如果过期键比例大于25%,则再对这个数据库进行抽样检查,否则检查下一个数据库。
9.5 RDB、AOF、主从复制对过期键的处理?
- RDB:执行save或bgsave命令生成的rdb文件会忽略过期键;
- AOF:aof文件不会自动处理过期键,但是重写aof文件会忽略过期键;
- 主从复制:主服务器启动时,会忽略过期键;从服务器启动时,会载入所有的键;但是主服务器会同步到从服务器,所以过期键也不会产生影响。
9.6 主从模式下的从服务器对过期键的处理?
主从模式下,客户端命令请求从服务器的过期键,从服务器不会删除它,且会正常返回值给客户端。
9.7 AOF文件对过期键的记录?
当过期键被删除时,会在aof文件追加del命令,显示删除过期键。
第10章 RDB持久化
10.1 RDB文件的作用?
RDB文件用于保存和还原redis服务器中所有数据库的所有键值对数据。
10.2 save命令与bgsave命令的区别?
- save:由服务器进程执行该命令,会导致服务器阻塞;
- bgsave:由子进程执行该命令,不会导致服务器阻塞。
10.3 redisServer的构造函数?
struct redisServer {
*saveparams; //save条件数组,数组中的每一个元素都保存一个save条件。
dirty; //更新计数器,记录距离上一次执行save命令到现在对数据库状态更新(增删改)的次数
lastsave; //上一次执行save命令的时间戳
}
10.4 serverCron函数的作用?
serverCron函数默认每100ms执行一次检测,当数组saveparams中有任意一个条件满足时,则执行save。
10.5 RDB文件解析?
- REDIS:标识这是一个redis的RDB文件;
- db_version:标识RDB文件的版本号;
- database:标识数据库的编号。
第11章 AOF持久化
11.1 AOF的实现原理?
AOF文件保存所有修改数据库的写命令来记录服务器数据库的状态。写命令会先保存在redisServer的aof_buf属性(AOF缓冲区),然后定期以redis协议的格式保存到AOF文件。
11.2 AOF的载入?
创建一个不带网络连接的伪客户端(redis命令必须在客户端上下文进行),并重复分析读取执行aof文件的写命令。
11.3 AOF重写的原理?
aof重写是通过读取当前服务器的数据库中键值对来实现的。重写aof在处理hash、list、set、zset时,如果值元素个数大于默认值(64),则会以多条命令记录键值。
11.4 如何处理AOF重写期间的写命令?
- 执行客户端命令请求;
- 将执行后的写命令保存到aof缓存区,定期同步到旧aof文件;
- 将执行后的写命令保存到aof重写缓冲区,当aof重写完成后,立即将aof重写缓冲区同步到新aof文件,最终完成新旧aof文件替换。
第12章 事件
12.1 redis事件是什么?
redis服务器是一个事件驱动程序,服务器处理的事件的类型主要分为时间事件和文件事件。
第13章 客户端
13.1 redis如何处理命令请求?
通过使用由I/O多路复用技术实现的文件事件处理器,redis服务器使用单线程单进程的方式处理命令请求,并与多个客户端进行网络通信。
13.2 redisServer.clients属性的作用?
clients属性是一个链表,用来保存所有的客户端状态,新建立连接的客户端会放在链尾。
13.3 redisClient.flags属性的作用?
表示客户端的角色和状态。
13.4 redisClient.querybuf属性的作用?
输入缓冲区,记录客户端发送的命令请求,最大不超过1GB。
第14章 服务器
14.1 redis服务器的作用?
redis服务器负责与多个客户端建立网络连接,处理客户端发送的命令请求,在数据库中保存客户端执行命令所产生的数据,并通过资源管理维持自身运转。
14.2 一个命令请求从发送到完成的过程?
- 客户端将命令请求发送给服务器;
- 服务器将命令请求保存到客户端状态的输入缓冲区,并分析出命令参数;
- 命令执行器根据参数查找命令的实现函数,然后执行并得到回复;
- 服务器将命令回复返回给客户端。
第15章 复制
15.1 redis主从复制是什么?
redis可以通过slaveof命令让一个服务器复制另一个服务器,其中被复制的服务器称为主服务器,进行复制的服务器称为从服务器。
15.2 主从复制的过程?
首先从服务器向主服务器发送psync命令;主服务器接收到从服务器的psync命令后,执行bgsave命令,并将该期间的写命令保存到缓冲区;主服务器生成rdb文件完成后,将rdb文件发送给从服务器,从服务器接收并载入rdb文件;主服务器再把缓冲区保存的写命令发送给从服务器,从服务器接收后执行写命令。
15.3 旧版本redis(2.8)主从复制的缺陷?
每次断线重连都会触发完整重同步,造成资源浪费,影响性能。且从服务器在载入rdb文件期间,会因为阻塞而无法处理客户端命令请求。
15.4 部分重同步的实现原理?
- 复制偏移量:主从服务器都会保存各自的复制偏移量。主服务器每次向从服务器发送N字节数据后,偏移量都会+N;从服务器每次接收N字节数据后,偏移量都会+N;通过比较主从服务器的偏移量,来判断主从服务器数据是否一致。
- 复制积压缓冲区:由主服务器维护一个固定长度的FIFO队列实现。在命令传播阶段(完成主从同步后),主服务器不仅会将写命令发送给所有的从服务器,而且还会将所有的写命令入队到复制积压缓冲区。当从服务器断线重连后,会通过psync命令发送复制偏移量offset;主服务器检查从服务器发送的复制偏移量offset是否存在于复制积压缓冲区内,如果存在则返回continue并执行部分重同步,否则返回fullresync并执行完整重同步。
- 服务器运行id:主从服务器都有自己的运行ID(由40个随机16进制字符组成)。在初始复制阶段,主服务器会将自己的运行ID发送给从服务器,从服务器接收后会将主服务器的运行ID保存起来;当从服务器断线重连后,会通过psync命令发送它所保存的主服务器运行ID;主服务器检查运行ID是否相同,如果相同再进行部分重同步的其他检查,否则进行完整重同步。
15.5 复制积压缓冲区大小调整?
建议调整为:second * write_size_per_second ;
second:从服务器断线重连的平均秒数;
write_size_per_second :主服务器平均每秒保存的写命令大小。
15.6 psync命令?
psync
初始复制阶段:psync ? -1
命令传播阶段:psync runid offset
第16章 Sentinel
16.1 Sentinel是什么?
Sentinel本质上是一个运行在特殊模式下的redis服务器,它与普通模式使用不同的命令表。
16.2 Sentinel的命令连接?
Sentinel与主从服务器建立命令连接与订阅连接(向其他Sentinel宣告自己的存在);Sentinel与其他Sentinel建立命令连接。
16.3 哨兵机制原理?
- 主观下线:Sentinel默认会以每秒一次的频率对实例(主服务器、从服务器、其他Sentinel)发送ping命令,通过实例的回复来判断该实例的状态。当实例连续回复无效的ping命令时,则认为该实例进入主观下线状态。
- 客观下线:当Sentinel发现某个实例进入主观下线状态时,会向同样监视该实例的Sentinel发起询问,然后统计主观下线投票,当主观下线投票达到一定的数量时,则认为该实例进入客观下线状态。
- 选举领头Sentinel:当Sentinel发现某个实例进入客观下线状态时,会在监视该实例的Sentinel范围内进行协商,选举出领头Sentinel来进行故障转移操作。
- 故障转移:选举出新的主服务器,修改从服务器的复制目标,将旧的主服务器变为从服务器。
第17章 集群
17.1 redis集群的作用?
redis集群通过分片进行数据共享,并提供复制和故障转移功能。
17.2 redis节点是什么?
一个节点就是一个运行在集群模式下的redis服务器。
17.3 节点的构造函数?
struct clusterState{
clusterNode myselg ; //指向当前节点的指针
dict *nodes ; //集群节点字典,键是节点名称,值是指向节点的指针
slots ; //记录集群中所有槽位指派的节点。slots[i] == *clusterNode表示槽位i由该节点处理
importing_slots_from ; //槽位导入数组,记录当前节点正在从其他节点导入的槽位
migrating_slots_to ; //槽位导出数组,记录当前节点正在导出到其他节点的槽位
}
struct clusterNode{
slots ; //记录当前节点处理哪些槽,如果slots[i] == 1,表示槽位i由当前节点处理
slaves ; //从节点数组,负责记录正在复制当前节点的从节点
}
17.4 集群的槽位有多少个?
集群一共有16384个槽位(slot),每个节点可以处理0或者最多16384个槽位。当集群的16384个槽位都有节点处理时,集群处于上线状态。
17.5 MOVED错误?
当客户端向节点发送与数据库键相关的命令时,接收命令的节点会计算出处理该数据库键的槽位,如果该槽位正好由当前节点处理,则直接执行命令;否则返回客户端MOVED错误,并指引客户端重定向(redirect)到正确的节点。
17.6 集群模式对数据库的限制?
集群模式下的各个节点只能使用索引0数据库。
17.7 集群重新分片?
redis集群重新分片能够将已经分配给源节点的槽位重新分配给目标节点,且相关的键值对也能完成迁移。
17.8 ASK错误和ASKING命令?
- ASK错误:在集群重新分片期间,源节点的槽位正在向目标节点迁移,此时如果客户端向源节点发送一条与数据库键相关的命令,源节点会先检查该键是否存在于当前数据库,如果存在,则直接执行命令;如果不存在,则检查源节点是否正在执行迁移槽,如果不是,则直接执行命令;如果是,则返回客户端一个ASK错误,并执行客户端重定向到目标节点。
- ASKING命令:客户端接收到ASK错误后,会先向目标节点发送一条ASKING命令,作为迁移槽临时标志(一次性使用),再将要执行的命令发送给目标节点。
17.9 主从节点?
redis集群的节点分为主节点和从节点,主节点负责处理槽位和命令请求,从节点负责复制主节点,并在主节点下线时,代替主节点。
17.10 集群节点的状态?
集群中的各个节点通过发送消息来交换各个节点的状态,包括:在线状态、疑似下线状态(PFAIL)、已下线状态(FAIL)。
17.11 投票下线?
在一个集群里,半数以上负责处理槽位的主节点间将某个主节点X上报为疑似下线状态时,那么该主节点X将被标记为已下线状态。当正在复制主节点X的从节点发现X主节点已下线,会向集群内广播消息发起投票选举,第一个向各个主节点要求投票的从节点将获得主节点的投票。
17.12 节点通信方式?
MEET、PING、PONG、PUBLISH、FAIL。
第18章 发布与订阅
18.1 发布订阅是什么?
redis的发布订阅功能由publish、subscribe、psubscribe等命令实现。执行subscribe命令,客户端可以订阅一个或多个频道;执行psubscribe命令,客户端可以订阅一个或多个模式。
18.2 redis服务器保存订阅关系?
struct redisServer{
dict *pubsub_channels; // 保存所有频道的订阅关系,字典键是某个被订阅的频道。字典值是订阅该频道的客户端链表
list *pubsub_patterns; //保存所有模式的订阅关系,链表中的每个节点都包含一个Pattern结构,记录被订阅的模式和单个客户端
}
18.3 服务器向客户端发送消息?
redis服务器首先在pubsub_channels所对应的频道订阅关系字典,根据消息键查找链表值,如果匹配则遍历发送消息;然后再遍历pubsub_patterns所对应的模式订阅关系链表,当有节点pattern与消息键匹配时,则发送消息。
第19章 事务
19.1 事务的概念?
redis通过multi、exec、discard、watch等命令实现事务。事务提供一种将多个命令请求打包,并一次性、按顺序执行的机制。多个命令请求会入队到事务队列,按照FIFO的顺序执行。在事务队列exec期间,redis服务器不会中断事务命令请求。
19.2 客户端命令执行流程?
19.3 watch命令的作用?
watch命令是一个乐观锁,它能在exec命令执行前,检测被监视的键是否至少有一个被修改,如果有,则拒绝执行事务,以确保事务的安全性。
19.4 watch命令原理?
redis服务器保存watch_keys字典,字典的key是被监视的键,value是由监视这个键的客户端所组成的链表。
19.5 ACID?
- 原子性:不支持事务回滚,执行错误不会终止事务(入队错误会,如:命令不存在、命令格式错误);
- 一致性;
- 隔离性;
- 持久性:由redis的持久化模式决定(如:always、在exec前加save)。
第20章 Lua脚本
20.1 lua脚本是什么?
redis从2.6版本开始通过在redis服务器中嵌入Lua环境,并对Lua环境进行修改,使得redis客户端可以使用Lua脚本。
第21章 排序
21.1 redis sort可以排序的对象类型?
redis的sort命令可以对列表键、集合键或者有序集合键的值进行排序。
21.2 sort命令的原理?
sort命令通过将被排序键包含的元素的指针载入到一个新的数组里,然后对数组进行排序来完成对键的排序(快速排序算法)。
21.3 STORE选项?
sort命令只向客户端返回排序的结果,而不保存排序结果。使用STORE选项,命令会将结果保存在指定的键里。
21.4 SORT命令的选项?
ASC、DESC、BY、LIMIT、STORE、GET
第22章 二进制位数组
22.1 redis对Bitmap的支持使用?
redis提供bitset、bitget、bitcount、bitop四个命令来处理二进制位数组。二进制位数据起始偏移量为0,二进制位的值为0或1。
22.2 bitcount的作用?
bitcount用于统计二进制位数组中值为1的二进制位的数量。
22.3 bitcount的实现原理?
查表算法(对进制数组的所有组合建表)和SWAR算法。
22.4 bitop的作用?
bitop可以对二进制位数组进行按位与(and)、按位或(or)、按位异或(xor)运算,复杂度为O(n2);取反(not),复杂度O(n)操作,并生成新的bitmap。
22.5 bitmap的实现?
redis使用SDS保存位数组,每单位字节长度位数组为一个buf,len表示buf数组的长度。每次setbit前检查buf数组是否需要扩展。
第23章 慢查询日志
23.1 慢查询日志的作用?
用于记录执行时长超过指定时长的命令。
23.2 慢查询日志的结构?
redis服务器将所有的慢查询日志都保存在redisServer.slowlog链表中,每个链表节点对应一个slowlogEntry结构,即一条慢查询日志。
23.3 慢查询日志如何处理超出数量限制的日志?
当redisServer保存的慢查询日志数量等于slowlog-max-len属性值时,在再插入的慢查询前,会先删除最旧的一条慢查询日志。
第24章 监视器
24.1 redis监视器是什么?
客户端通过执行monitor命令(客户端的REDIS_MONITOR标识会被打开),可以将自己转换成监视器,接收并打印服务器处理每个命令请求的相关信息。
24.2 redis监视器的原理?
服务器将所有的监视器都记录在redisServer.monitors链表中,每次处理命令请求前,都会遍历redisServer.monitors链表,将相关信息发送给所有监视器。