Redis 设计与实现

数据结构对象

简单动态字符串

  1. redis 使用SDS(simple Dynamic String)作为可被修改的字符串值;C字符串作为简单字面量(不可变更)。
  2. SDS 除了作为字符串值之外,还当做缓冲区;AOF模块中的缓冲区和客户端的缓冲区。
    在这里插入图片描述
  3. SDS遵循空字符串结尾的好处是可以使用C的部分函数。
  4. SDS的优势:
    • 降低获取字符串的时间复杂度
    • 杜绝缓冲区溢出(不用注意SDS的空间大小,在设置值的时候它会自动帮你分配大小)
    • 减少修改字符串带来的内存重分配次数(通过未使用空间,实现空间预分配和惰性释放空间)
    • 空间预分配原则:
      1. 少于1M;则len=free=待设置的数据的长度;
      2. 大于1M;len=待设置数据的长度 + free=1M
    • 惰性空间的释放:
      1. 使用free来保证惰性释放
      2. 通过API;在固定的时间对其free的空间进行释放。
  5. 二进制安全(由于SDS里面可以存储空字符串,而C字符串不行);

链表

  1. 列表键、发布订阅、慢查询、监视器的实现采用了链表

字典

定义:字典又称为符号表、关联数组、或映射;是一种用于保存键值对的数组。其结构如下:
在这里插入图片描述

  • type 属性指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,redis为不同类型的字典设置不同类型的函数。
    在这里插入图片描述

  • prividata 属性则保存了需要传递给那些特定类型函数的参数。

哈希表

redis的字典的实现是由hash表实现的。具体结构如下:
在这里插入图片描述

rehash

  1. 为字典表分配空间
    • 扩展:ht[1]的大小为ht[0].userd*2的2N次方;
    • 收缩:ht[1]的大小为第一个大于ht[0].used的2N次方。
  2. 重新计算hash值和索引;然后将键值对放在ht[1]中
  3. 迁移完成后释放ht[0];将ht[1]置位ht[0];

hash表的扩展与收缩:
以下任何一个条件都会触发hash的rehash操作:

  • 服务器目前没有在执行BGBSAVE和BGREWRITEAOF操作,且hash表的负载因子大于等于1;
  • 服务器目前没有在执行BGBSAVE和BGREWRITEAOF操作,且hash表的负载因子大于等于5;
  • 当hash表的负载因子小于0.1的时候会自动收缩。
    在这里插入图片描述
渐进式rehash

扩展或者收缩并不是一次执行完成;而是分多次、渐进式的完成。

hash表渐进式rehash步骤:

  1. 为ht[1]分配空间,让字典同时持有ht[1]和ht[0]
  2. 在字典中设置rehashIdx=0;表示rehash动作开始;
  3. 在rehash期间;用户对数据的增删改查都会导致ht[0]的数据被重新rehash到ht[1]中(ht[1]只会增加,ht[0]只会减少,总会达到ht[0]的size为0的时候,最后rehash完成);rehash操作完成后,对rehash自增1;

跳跃表

  1. 应用场景:有序集合、集群节点用作内部数据结构。
  2. zskiplist数据结构:
    在这里插入图片描述
    • header 头结点指针
    • tail 尾结点指针
    • level:当前层级最大值
    • length:跳跃表长度,当前包含节点数量
    • zskiplistNode数据结构:
      level:包含两个参数,前进指针(指向下一个节点)和跨度(当前指针和下一个指针的距离);遍历操作用指针即可完成,跨度可以计算出当前值的排位。
      backward 指针:后退指针,指向前一个节点的指针
      score:分值,排序用的,从小到大排列
      obj:成员对象

重点回顾

  • 跳跃表是底层有序集合的实现之一
  • 每个跳跃表的层高是1—32之间的随机数
  • 每个节点的成员对象必须是唯一的;
  • 跳跃表中的节点按照分支大小进行排序,当分值相同时,节点按照对象大小排序。

整数集合

  1. contents数组的真正类型取决于encoding属性的值(int16,int32,int64);
  2. 数据结构如下:
    在这里插入图片描述

升级

每当我们要将一个新元素添加到整数集合里面;并且该新元素的类型比整数集合类型要长的时候,那么整数集合先进行升级,再将整数添加到集合中。
步骤:

  1. 根据新元素的类型,扩展集合底层数组的空间大小,为新元素分配空间
  2. 将所有元素向上转型,且保证顺序不变
  3. 添加新元素至新的底层数组中

降级

整数集合不支持降级操作,一旦对数组进行升级,编码就会一直保持升级后的状态。

压缩列表

  1. 压缩列表是列表键和hash键的底层实现之一。

  2. 构成:由一系列特殊编码的连续内存块组成的顺序型数据结构。

  3. 压缩列表的节点构成:
    在这里插入图片描述

    • previous_entry_length:前一个节点的length;单位byte
    • encoding :记录了content的数据类型以及长度
    • content:内容(字节数组或int整数值)
  4. 连锁更新:由于previous_entry_length的长度可变(1byte或者5byte);如果存在多个长度在250-253之间的数据,在新增或者删除数据的时候容易发生连锁更新;最坏复杂度为O(N)(每个数据的更新需要分配N次的空间,有N个数据更新)。

对象

  1. 对象的类型与编码:5中类型的对象(字符串对象,列表对象,hash对象,集合对象,有序集合对象)
  2. 字符串对象(包含三种编码方式:int、embstr、raw)
  3. 列表对象;其编码可以使ziplist或者linkedlist;
    必须满足一下两个条件才能用ziplist:
    1、集合数量小于512
    2、字符串长度小于64byte
  4. hash对象;编码可以是ziplist或者hashtable(字典)
    • 保存了统一键值对的两个节点总是紧挨在一起,保存键的节点在前,值在后。
    • 先添加的hash键值在前(链表特性)
      两种结构如下:
      在这里插入图片描述

在这里插入图片描述
* 编码转换条件:
1、集合数量小于512
2、字符串长度小于64byte

  1. 集合对象;集合对象的编码可以是intset或者hashtable;如果采用hashtable,那么字典中的值全为null;如果是intset,则必须满足一下条件:

    1. 保存的所有对象必须是整数值
    2. 保存的对象不超过512个
  2. 有序集合对象;有序集合的编码可以是ziplist或者skiplist;ziplist编码的每个集合元素试用两个紧挨在一起的压缩列表节点实现;第一个保存元素成员,第二个保存元素成员的score。skiplist编码的有序结合对象试用zset结构作为底层实现;一个zset同时包含一个字典和一个跳跃表。
    在这里插入图片描述
    使用ziplist编码条件:

    • 数量小于128个
    • 成员长度小于64byte;
  3. AOF文件重写的实现;在执行BGREWRITEAOF命令时,REDIS服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程创建新的AOF文件期间记录服务器执行的所有写命令,当子进程完成创建新的AOF文件的工作后,服务器会将重谢缓冲区中所有内容追加到新的AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF重写操作。
    在这里插入图片描述

事件

Redis 服务器是一个事件驱动程序,服务器要处理以下两类事件:

  • 文件事件:Redis服务器通过套接字与客户端进行连接,而文件事件就是服务器对套接字操作的抽象。
    1. 文件事件处理器试用IO多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
    2. 当被监听的套接字准备好执行连接应答、读取、写入、关闭等操作时,操作相对应的文件事件就会产生。
  • 时间事件:redis服务器对定时操作的抽象,主要功能包含:
    1. 更新服务器的各类统计信息、时间、内存占用、数据库占用情况
    2. 清理数据库中的过期键值对
    3. 关闭和清理失效的客户端
    4. 尝试进行AOF和RDB操作
    5. 如果是主服务器那么对从服务器定期同步
    6. 如果是集群模式,对集群进行定期同步和连接测试

事件调度流程:
在这里插入图片描述

  1. 事件最大的阻塞事件是由到达事件的最近当前时间的时间事件决定的。这个方法既可以避免服务器对时间事件进行频繁轮询,也可以确保执行的阻塞时间过长
  2. 对文件事件和时间事件的处理都是同步、有序、院子的执行。服务器不会中途中断事件的处理,也不会对事件进行抢占。因此,他们都会尽可能减小阻塞的时间。(比如,对socket写入字节数超出限制之后,就会主动用break跳出写入循环,将数据保留至下一个事件循环中再次写入;再比如,fork子进程操作数据)
  3. 一般时间事件实际处理的时间都会比预定的要晚。

客户端

  1. redisClient 数据结构
    • fd 套接字描述符;(-1,表示伪客户端,大于-1表示普通客户端)
    • name 名称;默认为空,可以再客户端指定
    • flags 标志:标志记录了客户角色;表示客户端正在处理事情所处的角色,主从服务器、正在执行lua脚本、正在执行monitor命令等。。。
    • authenticated 身份验证
  2. 通常情况下,redis默认对没有修改数据状态的命令不会存储至AOF中;但PUBSUB和SCRIPT LOAD除外;前一个命令会导致客户端的状态改变,后一个命令会导致服务器的状态变更。
  3. 客户端类型:普通客户端、lua伪客户端、AOF伪客户端

服务器

多机数据库的实现

主从复制

旧版复制功能的实现

Redis的复制功能分为同步和命令传播;

  • 同步是指将从服务器的状态更新至主服务器当前所处的状态
    在这里插入图片描述

  • 命令传播是指主从服务器状态不一致时,使其变得一致。
    在这里插入图片描述
    旧版复制功能多的缺陷:

  1. 复制可以分为初次复制和断线复制;在断线复制中效率极其低下。
    在这里插入图片描述

新版复制功能的实现

利用psync命令替换了sync命令;psync命令具有完整重同步和部分重同步两种模式;

  • 完整重同步用于初次复制情况;与sync执行步骤基本一样;
  • 部分重同步则用于处理断线后复制的情况:如果条件允许,可以将主服务与从服务器断线这段时间的命令进行同步。

部分重同步到的实现:

  1. 复制偏移量
  2. 复制积压缓冲区(判断偏移量是否还在缓冲区中决定使用哪种模式的同步命令)
  3. 服务器的运行ID

sentinel

  1. sentinel 创建向主服务器的连接(会有两个连接;一个用来发送信息,一个用来订阅接收信息)

  2. 获取主服务器信息

  3. (获取从服务器信息)从主服务器信息里面得到从服务器信息

  4. 向从、从服务器发送信息(channel:sentinel:hello)

  5. 接收主、从服务器的消息(channel:sentinel:hello)

  6. 多个sentinel会同时接收同一个channel的消息;对于是自己发送的消息会丢弃,不是自己发送的消息会接收,并更新主服务器的事例结构。

  7. 创建连向其他sentinel的命令连接(每个sentinel都和其他sentinel有连接)
    在这里插入图片描述

  8. sentinel 是一个运行在特殊模式下的Redis服务器,他使用了和普通模式不同的命令列表。

集群

  1. 节点
  2. 槽指派;当数据库中16384个槽都有节点在处理时,集群处于上线状态;相反的,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态。
  3. 节点只能使用0号数据库。
  4. 使用跳跃表保存槽和键的关系
  5. 重新分片的原理:
    在这里插入图片描述
  6. ASK错误;是两个节点在迁移槽的过程中使用的一种临时措施;通过发送asking,再来执行命令;可以获得正确的操作回复。
  7. 消息
    • MEET 消息(加入集群的消息)
    • PING 消息(定时心跳和超时pong检测的心跳之后发送心跳)
    • PONG 消息(MEET和PING发送消息的返回信息,告知已接收信息)
    • FAIL 消息 (收到其他节点的FAIL状态后,会向所有节点广播FAIL消息)
    • PUBLISH 消息(当节点接收到PUBLISH命令时,节点会执行这个命令,并向集群广播一条PUBLISH命令)

独立功能的实现

  1. 客户端可以订阅一个或多个频道;从而成为订阅者。

  2. 订阅分为订阅频道和订阅模式;订阅频道是一个固定的名称,订阅模式是一个匹配的名称,匹配到的频道都会被订阅。客户端订阅命令:SUBSCRIBE;客户端订阅模式命令:PSUBSCRIBE;

  3. redis的事务的ACID性质

    • 原子性
    • 一致性
      • 入队错误;redis2.6.5以前,同一个事务里面如果有错误命令,那么错误命令不会执行,但是正确命令会执行;2.6.5以后,同一个事务会保证一致性。
      • 执行错误;执行错误一般都发生在入队时没有发现错误,在实际执行时发生错误导致数据不一致最常见的错误就是类型错误
        在这里插入图片描述
      • 服务器停机(不会导致数据不一致)
    • 隔离性(单线程,所以隔离)
    • 持久性
  4. lua 脚本

  5. sort 命令

  6. 二进制位数组

    • setBit
    • getBit
    • bitOpt
    • bbitCount(汉明重量算法)
  7. 慢查询日志

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值