《Redis设计与实现》简读

2019-01-02  本文已影响0人  佐柱

一、数据结构与对象

简单动态字符串(SDS)

关于空间预分配和空间惰性释放

  • 字符串增长操作时,如果修改后长度小于1M则分配该字符串长度2倍的内存空间,如果修改后长度大于等于1M则分配该字符串长度+1M的内存空间。(预分配,避免每次增长操作都需要进行内存重分配执行系统调用)
  • 字符串缩短操作时,程序不会立即释放缩短后多出来的字节,而是在需要时再释放。(惰性释放,避免以后需要增长操作时重分配内存,会在较短的时间内造成内存浪费,文中未提及何时是“需要时”)

最佳实践:因为对字符串的增长或缩短操作都有可能需要执行内存重分配,所以修改相同键使用SDS类型保存的值时保持修改前后长度一致。

链表

字典

rehash步骤

  1. 扩展操作(没有执行BGSAVE或BGREWRITEAOF且负载因子大于等于1;正在执行BGSAVE或BGREWRITEAOF且负载因子大于等于5),为ht[1]分配第一个大于等于当前包含键值对数量(ht[0].used)*2的<math><msubsup><mi>2</mi><mi></mi><mi>n</mi></msubsup></math>内存空间
  2. 收缩操作(负载因子小于0.1时),为ht[1]分配第一个大于等于当前包含键值对数量的<math><msubsup><mi>2</mi><mi></mi><mi>n</mi></msubsup></math>内存空间
  3. 将保存在ht[0]中的所有键值对rehash到ht[1]
  4. 释放ht[0],将ht[1]设置为ht[0],创建新的空白哈希表ht[1]

负载因子=哈希表已保存节点数量/哈希表大小

Redis使用MurmurHash2算法来计算键的哈希值

跳跃表

整数集合

升级步骤

  1. 根据新元素的类型扩展底层数组空间,并为新元素分配空间
  2. 转换现有元素至新的类型,保持有序性放置元素
  3. 添加新元素,当新元素小于所有先有元素时放置在索引0,当新元素大于所有先有元素师放置在索引length-1

最佳实践:为了避免添加新元素时产生升级操作,应向同一整数集合添加相同类型的整数

压缩列表

对象

<div align=center>不同类型和编码的对象</div>

类型 编码 对象
REDIS_STRING REDIS_ENCODING_INT(整数值) 使用整数值实现的字符串对象
REDIS_STRING REDIS_ENCODING_EMBSTR(小于32字节字符串) 使用embstr编码的简单动态字符串实现的字符串对象
REDIS_STRING REDIS_ENCODING_RAW(大于32字节字符串) 使用简单字动态字符串实现的字符串对象
REDIS_LIST REDIS_ENCODING_ZIPLIST(默认配置下,所有元素长度小于64字节且元素数量小于513,查看命令:CONFIG GET list-max-ziplist*) 使用压缩列表实现的列表对象
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用双端链表实现的列表对象
REDIS_HASH REDIS_ENCODING_ZIPLIST (默认配置下,所有元素长度小于64字节且元素数量小于513,查看命令:CONFIG GET hash-max-ziplist*) 使用压缩列表实现的列表对象
REDIS_HASH REDIS_ENCODING_HT 使用字典实现的哈希对象
REDIS_SET REDIS_ENCODING_INTSET(默认配置下,所有元素都是整数值且元素数量小于513,查看命令:CONFIG GET set-max-intset-entries) 使用整数集合实现的集合对象
REDIS_SET REDIS_ENCODING_HT 使用字典实现的集合对象
REDIS_ZSET REDIS_ENCODING_ZIPLIST(默认配置下,所有元素长度小于64字节且元素数量小于128,查看命令:CONFIG GET zset-max-ziplist*) 使用压缩列表实现的有序集合对象
REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳跃链表和字典实现的有序集合对象

备注

  1. TYPE KEY(获取键的对应值对象类型)
  2. OBJECT ENCODING KEY(获取键的对应值对象编码)

内存回收、对象共享、空转时长度

最佳实践:为了最大程度的节省内存,应将简单字符或重复率较高的字符串对应成0-9999范围内的数字。

二、单机数据库的实现

数据库

惰性删除:当读取的键是一个过期键时才会将该键删除并返回空。

定期删除:在规定的时间内分多次遍历每个数据库,从expires字典中随机检查一部分键的过期时间(也即每次执行定期删除并不一定能把所有的过期键都删除)。

最佳实践:主从模式下从服务器在读取到过期键时不会主动删除且会当成正常键返回数据,当数据中包含较多的过期键时主服务器的定期删除策略可能需要较长时间才能将该过期键删除,因此Redis的主从模式不同于Mysql的主从模式(主写从读),如果使用类似Mysql主从的用法时需注意过期数据在一定时间内可能是脏数据。

RDB持久化

AOF持久化

事件

客户端

  • 可变输出缓冲区分普通客户端、pubsub(发布/订阅模式)、slave三种客户端限制。默认情况下,普通客户端无限制(阻塞式的消息应答模式通常不会造成输出缓冲区堆积),pubsub客户端超过32m或持续60s超高8m,slave客户端超高256m或持续60s超过64m,对于超过限制的客户端Redis将关闭连接。
  • 最大连接数受系统当前文件描述符数量限制,最大只能取文件描述符数量限制-32(Redis最多会占用32个文件描述符)。
  • 如果客户端是主服务器、从服务器、被BLPOP等命令阻塞、正在执行SUBSCRIBE等订阅命令,将不受timeout设置影响。

服务器

命令请求步骤

  1. 客户端将命令请求发送给服务器
  2. 服务器读取命令请求并解析出命令参数
  3. 命令执行器根据参数查找命令的实现函数,执行实现函数并得出命令回复
  4. 服务器将命令回复返回给客户端

服务器启动步骤

  1. 初始化服务器状态
  2. 载入服务器配置
  3. 初始化服务器数据结构
  4. 还原数据库状态
  5. 执行事件循环

三、多机数据库的的实现

复制

Sentinel(哨兵)

集群

MOVED错误表示所请求的键负责权已经转移到另一节点,ASK错误则只是槽正在转移时的一种临时性错误

四、独立功能的实现

发布与订阅

事务

Lua脚本

Redis创建Lua执行环境步骤

  1. 创建基础Lua环境
  2. 载入函数库到Lua环境中
  3. 创建包含对Redis进行操作的函数的全局表格
  4. 使用自制随机函数替代Lua原有带副作用的随机函数(自制随机函数具有以下特征:①对于相同seed,math.random总产生相同的随机数序列;②除非显示修改math.randomseed中的seed,否则均使用math.randomseed(0)初始化seed)
  5. 创建排序辅助函数,Lua环境使用该函数对一部分Redis命令的结果进行排序
  6. 创建可以提供更多详细错误信息的错误报告辅助函数redis.pcall
  7. 保护Lua环境的全局变量,防止执行脚本过程中修改全局变量
  8. 将修改完成后的Lua环境保存到服务器状态的Lua属性中

排序

慢查询日志

上一篇 下一篇

猜你喜欢

热点阅读