文章目录
Redis设计与实现
第二章 字符串
SDS是redis的默认字符串表示。
- SDS用含有
len,free
两个变量和一个字符串数组来表示,len表示已保存的字符串长度,free表示其中未使用的字节数量。 - 好处
- 获取一个SDS的长度时间复杂度为O(1)
- 拒绝缓存区溢出,当SDS的API要对SDS进行修改的时候,会先判断SDS的空间够不够,不够的话进行扩充,再接着执行。
- 减少修改字符串时带来的内存重分配次数。
- 空间预分配
- 修改之后(len)的长度小于1MB,那么程序分配和len属性同样大小的未使用空间。
- 假如长度大于1MB,那么程序会分配1MB的未使用空间。
- 惰性释放空间
- 当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收,而是使用free属性将这些字节的数量记录起来,并等待将来使用。
- 空间预分配
- 二进制安全
- 不在意写入数据是啥样的,怎么进去就怎么出来。所以SDS的buf被叫做字节数组。
- 兼容C字符串函数
- 会在分配字符串的时候再多分配一个字节来容纳这个空字符。
第三章 链表
列表键的底层实现之一
第四章 字典
数据库和 哈希键的底层实现之一
redis字典使用哈希表作为底层实现,一个哈希表里面有多个哈希表节点,每个哈希表节点就保存了字典中的一个键值队。
-
哈希表结构体含有
typedef struct dictht{ dictEntry **table;//每个元素指向一个键值对数组 unsigned long size;//哈希表大小 unsigned long sizemask;//等于size-1,和哈希值一起决定一个键应该放到哪个table数组上 unsigned long used;//目前已有节点数量。 }dictht;
-
字典结构
typedef struct dict{ dictType *type;//类型特定函数 void *privdata;//私有数据 dictht ht[2];//哈希表 int trehashidx;// }
一般只使用
ht[0]
,ht[1]
只会再对ht[0]
进行rehash
的使用 -
hash冲突的时候,采用链地址法来解决键冲突,头插法。
-
rehash
- 首先,先对
ht[1]
分配空间,假如是要扩展hash就设置空间为第一个大于等于ht[0].used*2的2^n
。假如是要缩小,就设置空间为第一个大于等于ht[1].used得2^n
- 之后将ht[0]得键值对rehash到ht[1]上。
- 之后释放ht[0],将ht[1]设置为ht[0],并再ht[1]上创建一个空白哈希表。
- 首先,先对
-
渐进式rehash
- 首先为ht[1]分配空间。
- 在字典中维持一个索引计数器变量rehashidx,并将它设置为0,表示rehash工作正式开始。
- 在进行期间,每次对字典添加,删除,查找和更新操作得时候,除了执行特定操作之外,还会将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1]上面,当rehash工作完成后,将rehashidx属性+1。
- 当rehash操作完成的时候,会将rehashidx设置为-1。
- 好处,将rehash计算工作均摊到每个增删改查之上,避免了集中式rehash而带来的庞大计算量。
第五章 跳跃表
有序集合键的底层实现之一
跳跃表有点类似于ST表,对数据的操作是logN。
- 跳跃表按照分值排序,分层是用来加快访问速度的,而跨度是指到下一个节点的距离。后退节点。分值一样的情况下,按照成员对象在字典序中的大小进行排序。
- 跨度经常是不一样的。查找数据可以通过不同层进行一个跳跃。
第六章 整数集合
是集合键的底层实现之一。
其结构体含有编码方式(int16,int32,int64),元素数量和元素数组
- 升级:将低编码转高编码。
- 先扩充空间,然后从后面往前移动数据到对应位置。
- 好处:提升灵活性,节约内存(在有必要的时候才升级)
- 降级:不支持
第七章 压缩列表
是列表键和哈希键的底层实现之一。为了节约内存开发的顺序数据结构。
- 会产生连锁更新,遍历压缩列表的时候都是从后面开始遍历的,因此中间有一个叫
previous_entry_length
的属性,假如插入一个比较大的节点(大于等于254)该插入节点的下一个节点会发生变化。下一个节点变化了,该下一个节点的下一个节点的previous_entry_length
也需要变更。 - 删除节点也可能发生连锁更新。
- 但是连锁更新的概率不会很大,就算发生了,只要影响的节点不多,也不会造成性能问题。
第八章 对象
对象是将前面7章的内容结合起来的。
- redis采用引用技术的内存回收机制。
- 服务器初始化的时候会生成0到9999的字符串,之后只要用到就用引用技术。
- redis的类型多态和编码的多态。
第九章 数据库
redis中的所有内容都采用kv存储。
redis中的键过期是可以存放在一个expire字典里面的
- 可以通过expire key ttl设置过期时间。
过期键有三个删除策略
- 定时删除,定时后对其进行键的删除操作。
- 对内存友好,及时释放掉过期键内存。
- 对cpu不友好,当过期键过多的情况会占用一部分cpu时间。
- 创建定时器需要用redis服务中的时间事件,而当前时间事件的实现方式是无序链表,查询时间是O(N),因此不太现实。
- 惰性删除,放任键过期不放,取的时候检查,如果过期就删除,没过期就返回该键。
- 对cpu友好。
- 对内存不友好
- 定期删除,每隔一段时间,程序对数据库检查一次,删除其过期键。
- 对定时和惰性删除的折中方案。
- 假如太频繁,退化为定时策略,cpu过多时间消耗在删除过期键上。
- 假如太少,退化为惰性,浪费内存。
第十章 RDB持久化
- RDB持久化可以通过SAVE或者BGSAVE进行,也可以在配置文件中设置定时规则。SAVE使用的时候会阻塞,而BGSAVE是会另外开一个子进程进行的。
- BGSAVE执行过程,SAVE会被服务器拒绝,BGSAVE会被服务器拒绝,两者都是怕发生竞争条件。
- BGREWRITEAOF和BGSAVE不能同时进行
- 假如BGSAVE正在执行,那么客户端发送的BGREWRITEAOF会被延迟到BGSAVE结束后执行。
- 假如BGREWRITEAOF正在执行,那么BGSAVE会被服务器拒绝。
- RDB持久化的载入是在程序启动的时候发生的。
- 假如开启了AOP持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。除非AOP持久化关闭了,数据库才会使用RDB文件来还原数据库状态。载入过程会处于阻塞状态。
- RDB持久化,假如是在配置文件中设置的话,内部就会产生一个saveparam数组。
第十一章 AOP持久化
-
AOF持久化功能的实现可以分为命令追加,文件写入,文件同步三个步骤。
-
伪客户端(fake client)来实现AOF文件的载入。
-
AOF的重写会去除一些冗余的指令。
-
AOF重写缓冲区会在子进程进行重写的时候收集父进程的写入命令。
第十五章 复制
- 旧版本的复制功能主要分为同步和命令传播两个操作。
- 同步用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。
- 从服务器向主服务器发送SYNC命令。
- 收到SYNC命令的主服务器执行BGSAVE命令,生成RDB文件,之后将该从文件发送给从服务器,从服务器接受并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE时的数据库状态。
- 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新到主服务器数据库当前所处的状态。
- 命令传播操作则用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态。
- 是指在完成同步之后,当客户端发送命令给主服务器,主服务器也得将写命令发送给从服务器进行同步。
- 同步用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。
- 旧版的复制主要有两种:初次复制,断线后复制,断线后复制效率非常低,因为是从头开始的。
- 新版的PSYNC命令具有完整重同步和部分重同步。
- 完整重同步用于初次复制情况,就是创建RDB文件以及缓冲区缓冲发送过程中的写命令在进行同步。
- 部分重同步用于处理断线后重复情况,就是单纯传递那些缺少的数据,类似超时重传。
- 部分重同步构成
- 复制偏移量,主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N。
- 从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的偏移量的值加上N。
- 复制积压缓冲区
- 固定长度的先进先出队列,当从服务器断线重连后,假如所需要的数据在该复制积压缓冲区中有的话,就使用部分重同步,假如没有就使用完整重同步。
- 该复制积压缓冲区的大小默认为1MB,建议为每秒传送的数据量*重连的最长时间。
- 服务器运行ID
- 从服务器对主服务器初次复制时,主服务器会将自己的运行ID传送给从服务器,从服务器就会保存起来。当断线重连之后,根据该ID来判断所连接的是不是之前的服务器,可以尝试执行部分重同步操作。假如不相同就执行完整重同步操作。
- PSYNC发送出去可能返回的三种结果
- 如果主服务器返回
+FULLRESYNC <runid> <offset>
就可以进行完整重同步操作,runid是这个主服务器的运行ID,从服务器会将这个ID保存起来,在下次发送PSYNC时使用,offset是主服务器当前的复制偏移量,从服务器会将这个值作为自己的初始化偏移量。 - 如果主服务器返回
+CONTINUE
回复,那么就需要执行部分重同步。 - 如果返回
-ERR
,说明主服务器版本低于Redis2.8,就需要发送SYNC命令。
- 如果主服务器返回
- 复制的实现
- 设置主服务器的地址和端口
- 建立套接字连接
- 发送PING命令,一方面可以检查套接字连接是否正常,二是检查主服务器能否正常处理命令请求。
- 超时,网络连接状态不佳,不能继续执行复制工作后续步骤。
- 错误,主服务器无法处理从服务器命令请求,断开,重新连接。
- PONG,说明正常。
- 身份验证
- 假如设置了masterauth,需要身份验证。从服务器向主服务器发送一条AUTH命令,命令的参数为从服务器masterauth选项的值。
- 如果主服务器设置了requirepass,且从服务器没有设置masterauth选项,那么主服务器将继续执行命令,复制继续进行。
- 如果从服务器通过AUTH命令发送的密码和主服务器requirepass选项所设置的密码相同,那么主服务器将继续执行从服务器发送的命令,复制工作可以继续进行,假如密码不同,主服务器返回一个invalidpassword错误。
- 如果主服务器设置了requirepass,但是从服务器没有设置masterauth选项,那么主服务器返回一个NOAUTH错误。
- 如果主服务器没有设置requirepass,但是从服务器却设置了masterauth选项,那么主服务器将返回一个no password is set 错误。
- 假如没有设置,就不进行身份验证。
- 假如设置了masterauth,需要身份验证。从服务器向主服务器发送一条AUTH命令,命令的参数为从服务器masterauth选项的值。
- 发送端口信息。
- 同步信息
- 命令传播
- 心跳检测:从服务器默认会以每秒一次的频率,向主服务器发送命令。
- 检测主从服务器的网络连接状态。
- 辅助实现min-slaves配置选项。
- 检测命令丢失。