redis设计与实现 pdf_Redis设计与实现

本文详细介绍了Redis的数据结构和实现,包括简单动态字符串sds、链表linkedlist、字典dict、有序集合zset、整数集合intset、压缩列表ziplist、对象redisObject、RDB和AOF持久化策略,以及文件事件和命令请求处理。Redis利用各种高效的数据结构,如跳跃表和压缩列表,实现了高性能的键值存储。同时,通过惰性和定期删除策略处理过期键,确保数据的实时性。

1ce4525c60f1e1448ac5ebfcfa87daa6.png

简单动态字符串sds

  • 安全兼容部分C 字符串函数
  • 长整型或浮点数通过字符串保存

链表linkedlist

  • 由listNode组成的双端双向链表。通过dup(),free(),match()函数实现多态

字典dict/hashtable

  • 双哈希表,哈希算法采用MurmurHash,链式存储解决冲突

866e08c57e73266ffee97c7aab817215.png
  • rehash通过将ht[0]中的所有键值对rehash到分配更多/更少空间的ht[1]后切换
  • 在执行 BGSAVE 或BGREWRITEAOF 的创建子进程中,提高负载因子以避免子进程存在期间进行rehash
  • 渐进式rehash,避免了集中式 rehash 而带来的庞大计算量,均摊每次对字典执行添加、删除、查找或者更新操作时,将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1],然后自增rehashidx,相当于每次转移一行。渐进式rehash期间,RUD操作在两表上都进行,加入C操作在ht[1]上,保证ht[0]只减不增加

有序集合zset

18c2f2d5f3d71adc1ae91c8f46d4d4aa.png
  • 有序集合zset包含1个字典dict和1个跳跃表zskiplist。让有序集合的查找和范围型操作都尽可能快地执行
  • 有序有排位zskiplist。zskiplistNode根据幂次定律生成高度,每层有level[i].forward前进指针,level[i].span记录同层间两顶点的跨度(用于计算rank=sum(span))。节点根据score排序,分值相同的节点将按照成员对象obj在字典序中的大小来进行排序
  • zskiplist的CRUD操作时间复杂度在O(logN)

整数集合intset

633bb8500cbc2a6bbdcd02c505d16875.png
  • 保存整数值int16/int32/int64的集合,通过encoding的int8有序数组实现
  • 自动进行自动升级与降级,通过空间重分配+从后向前复制实现

压缩列表ziplist

b34445be7d84b5e64795925bd4fdcd05.png
  • 特殊编码的连续内存块组成的有序列表,编码字节数组或整型数组
  • 从表尾向表头遍历:只要我们拥有了一个指向某个节点起始地址的指针, 那么通过这个指针以及这个节点的 previous_entry_length 属性, 就可以一直向前一个节点回溯
  • 查找R为O(N),NextNode/PrevNode为O(1)。CUD操作可能引发连锁更新,让每个节点的previous_entry_length 属性都符合压缩列表对节点的要求,执行 N 次空间重分配操作,时间复杂度平均O(N),最坏在O(N^2)。适合少量元素

对象redisObject

d50542f9807e2def478c1dd924269cf0.png
  • redisDb使用dict保存所有的键值对,键空间是个dict,键总是一个字符串对象,值是redisObject对象,可以是字符串对象string、列表对象list、哈希对象hash、集合对象set、有序集合对象zset之一,通过encoding采用不同的数据结构实现,对象间可以动态转换
  • 字符串对象的编码可以是 int、raw(sds) 或者 embstr(embstr编码的sds)
  • 列表对象保存的所有字符串元素的长度都小于64字节或者元素数量小于512个,采用ziplist,否则采用linkedlist
  • 哈希对象的编码可以是 ziplist 或者 hashtable
  • 集合对象的编码可以是 intset 或者 hashtable(值为null)
  • 有序集合的编码可以是 ziplist 或者 skiplist(zset)
  • 通过引用计数refcount回收对象内存
  • 只对包含整数值的字符串对象进行共享,因此检查给定的共享对象和键想创建的目标对象是否完全相同需要消耗CPU
  • lru属性用于淘汰
  • FLUSHDB/DBSIZE/EXISTS/RENAME/KEYS操作键空间(key space)。SUBSCRIBE获取键空间的变化通知或键事件通知
  • 命令除了对键值对进行读写,还有维护键空间操作,比如统计keyspace_hits/keyspace_misses属性,更新lru属性,watch后标记dirty属性
  • redisDb的expires字典保存数据库中所有键的过期时间,键空间的键和过期字典的键都指向同一个键对象,值为long long型的过期绝对时间
  • 过期键删除策略:惰性删除,所有读写数据库命令在执行之前都会调用expireIfNeeded函数对输入键进行检查;定期删除,serverCron周期性调用activeExpireCycle,在规定时间内分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键,减少删除操作对CPU的影响。slave的过期键删除动作由master控制

RDB持久化

0ab5684a8e1937438a1daf1e46bd925b.png
  • 将所有键值对序列化为 expiretime_type_key_value_pairs
  • 过期键不会对生成新的RDB文件造成影响
  • 载入RDB文件时过期键会被忽略

AOF持久化

4edca9987217cdcb2e51177e770d0f91.png
  • 可为命令追加(append),文件写入,文件同步(sync)3个步骤
  • redisServer将写命令追加到aof_buf缓冲区(sds实现)
  • 在eventLoop中执行AOF
  • 过期键执行删除时向AOF文件追加一条DEL命令,来显式地记录该键已被删除
  • AOF重写时过期键会被忽略

文件事件

6648f342532d13cd6e7a0dab6900fb1e.png
  • Reactor模式的文件事件处理器。I/O 多路复用监听多个套接字并关联事件处理器,然后通过队列有序同步地传给FileEventHandler来执行
  • 文件事件是对套接字操作的抽象,比如accept/read/write/close等操作都会产生一个AE_READABLE 事件(读事件)或 AE_WRITABLE 事件(写事件),多事件会并发出现并处理

命令请求

3604d8680ecca60d1a009dfe5848d72a.png
  • 服务器通过redisClient保存的客户端状态
  • 命令请求处理器解析RESP协议的命令请求,并保存到客户端状态中。回复保存在客户端状态的输出缓冲区(buf与replay)里
  • 命令执行器根据命令表(dict)查找出redisCommand命令保存在cmd
  • 执行前预备操作去检查当前状态是否可执行(上次BGSAVE是否出错/正在SUBSCRIBE/正在执行事务/执行Lua并阻塞);满足条件则调用cmd->proc(client)执行命令;命令实现函数会将命令回复保存到客户端的输出缓冲区里面, 并为客户端的套接字关联命令回复处理器, 当套接字变为可写状态时, 服务器就会执行命令回复处理器, 将保存在客户端输出缓冲区中的命令回复发送给客户端
  • 执行完后的后续操作:记录慢查询日志;AOF;复制命令给slave

参考

  • 《Redis 设计与实现》http://redisbook.com/ 。redis3.0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值