Redis设计与实现总结

article/2025/10/14 5:10:39

  本文总结自《Redis设计与实现》一书,只打算总结Redis底层数据结构的实现。Redis的使用参考我的另一篇笔记Redis操作指南。

1 Redis概览

  Redis是一个C语言编写的开源、非关系型内存数据库。它底层属于单线程、全内存操作,提供对象共享、引用计数和对象回收功能。它通过SDS(简单动态字符串)、链表、字典、跳跃表、整数集合和压缩表这几种简单数据结构,实现了五种对象:String、List、Set、ZSet、Hash。和对象同级别的数据结构和功能包括:计数统计、地理空间、发布订阅和bitMap操作。上层,Redis提供事务、主从复制、脚本、LRU驱动事件功能。高可用方面,Redis可以搭建集群,哨兵+自动选举机制保障,可以持久化、故障转移。
在这里插入图片描述

2 Redis数据结构

2.1 SDS

  SDS简单动态字符串,这个名词区别于C语言中的字符串。C字符串以\0结尾,SDS不用,它的结构中有记录字符串长度和剩余空间长度的字段,buf指向真正保存字符的数组起始位置。
在这里插入图片描述

2.2 链表

  Redis使用的是双向链表,由List和ListNode(节点)构成,值存储在节点下。
在这里插入图片描述

2.3 字典

  字典的ht数组指向哈希表,type保存了指向各种函数的指针,rehashindex != -1时表明字典正在做reHash操作。哈希表指向一个entry数组,里面保存了NULL或者entry,根据负载因子决定是否扩容并重算哈希值。entry保存了键值对和指向下一个entry的指针next,当有两个以上entry的哈希值相同时,entry通过next指针连接成一条链来解决哈希冲突(拉链法)。可以看到它的 实现和Java中的HashMap实现非常相似,不过JDK8以上的HashMap不再需要重算哈希,而且entry链长达到8以后会转换成红黑树提高查询效率。
在这里插入图片描述

2.4 跳跃表

  一种类似链表的结构。链表的查询必须不断查询next,每次只能跳过一个节点,而跳跃表的next在不同的层可以跳过多个节点,它的查询可从高层向底层二分查找,直到找到需要的节点。节点中的BW指向前一个节点,score值必须从小到大,value存储节点值。每一层保存跨度和指向下一个节点的指针。跨度用来计算节点的Ranke(排位),例如下图标节点[3],从头结点L3开始累加跨度:1+2=3,得出标节点[3]排第三。
在这里插入图片描述

2.5 整数集合

  存储了整数数组信息的数据结构。为了节省空间,它可以自动升级16->32->64bit,但是不能降级。它存储的元素都是升序排列且没有重复元素的。
在这里插入图片描述

2.6 压缩列表

  压缩列表是由特殊编码的连续内存块组成的顺序型数据结构,为节约内存而生。它的entry数量不定,可根据实际情况增减。encoding代表content里面是字节数组还是整数值,还存储了content的长度。
在这里插入图片描述

3 Redis对象

  Redis的对象把类型和底层数据结构做了抽象,对象的类型和具体数据结构通过常量/指针的方式来决定。一个存储类型的属性(type),一个存储编码方式(encoding,也就是底层数据结构类型 ) 的属性,一个指向底层数据开始位置的指针(ptr),一个保存最后访问时间的指针(lru)和一个引用计数(redcount,用于对象共享和对象回收)。Redis的基本数据结构撑起了Redis的五种数据类型(对象):String、List、Set、ZSet、Hash。
在这里插入图片描述
类型和编码对照表

4 Redis数据库

  Redis数据库大框架主要是由RedisServer、RedisClient、RedisDB三个结构体撑起来的。RedisServer记录RedisDB的数量和位置,RedisClient记录当前客户端连接指向的RedisDB,RedisDB则存储字典,字典中存储字符串值和5种类型的对象,这就把前面的基本数据结构、5种对象串了起来。下图完美的展示了这幅逻辑视图。
在这里插入图片描述

5 一些功能特性的原理

5.1 键空间

  Redis数据库的 *dict 字典数组保存了所有的键值对,这个字典称为键空间,键空间的键就是数据库的键,键空间的值就是数据库的值,对数据库键值对的所有操作都是在键空间上的操作。

5.2 过期字典

  过期字典保存了所有键的过期时间,它首先是一个字典,它的键是一个指针,值是long类型的毫秒精度UNIX时间戳(为了简洁,下图的key-value没有按照真实的字典来画)。
在这里插入图片描述

5.3 过期删除策略

  定时删除:设置键过期时间的同时创建定时器,由定时器定时删除。
  惰性删除:获取键的时候判断是否过期,过期则删除,否则返回查询结果。
  定期删除:每隔一段时间就遍历检查一遍,删除已过期键,保留未过期键,每遍不一定全部删除。
Redis采用惰性删除和定期删除,提高单线程的效率。

5.4 持久化

  先说一下RDB和AOF的恢复逻辑,服务器启动后会检查AOF持久化功能是否开启,若开启则载入AOF文件,否则载入RDB文件:
在这里插入图片描述

5.4.1 RDB

  持久化:可以用save命令手动执行持久化,也可以在redis.conf配置文件设置多条自动持久化条件“save m n ”,意思是在m秒内执行了n次更新就触发RDB。持久化产生的文件后缀是 .rdb,默认在Redis根目录。RDB文件结构:
在这里插入图片描述
  恢复:RDB的恢复在Redis重启后自动执行。

5.4.2 AOF

  持久化:类似MySQL的binlog,保存客户端命令实现持久化,会追加客户端命令到一个缓冲区然后按某种策略保存到磁盘文件。AOF重写类似把MySQL数据备份成insert语句组成的文件,redis会fork一个子线程,然后根据当前数据库状态生成尽量少的命令保存到文件。
  恢复:创建一个不带网络功能的伪客户端,从AOF文件循环读取命令并执行。

5.5 文件事件和时间事件

  Redis的文件事件是指网络事件,Redis的事件处理是单线程,它采用IO多路复用来管理客户端连接,然后用事件分派器把就绪事件分发到不同的事件处理器,事件分派和处理在同一条线程中,其事件模型结构如下:
在这里插入图片描述
  Redis的时间事件放在一个无序链表中,每当时间事件执行器运行就会遍历检查,执行已到期的事件。
在这里插入图片描述
时间事件和文件事件的处理也是在同一条线程中,它的逻辑如下:

def aeProcessEvents(){获取将会最先到期的时间事件;计算该事件是否已到期;if (未到期) {阻塞接收文件事件直到上述事件到期;} else {接收文件事件,立马返回,不阻塞;}处理所有文件事件;处理所有时间事件;}

5.6 Redis常见拓扑关系

  下面介绍的几种拓扑结构其配置均不相同,特别是主从模式与集群模式,集群模式也有主从配置,然而配置方式与主从模式完全不同,博主也是踩了不少坑,而且搜遍网络竟然发现没有一篇文章能够讲清楚。Redis的高可用提供了自动故障恢复(failover)功能,很多文章把主从模式配置讲的细之又细,却断然不做故障恢复测试,又或者通篇都在讲故障恢复的理论却一字不提如何配置。下面的配置基于Windows,博主已经准备好一个windows版的集群配置,可以在这里下载,主从模式稍加修改就可以测试。

5.1 单机单点模式

  这种模式下只有一个Redis服务节点,特点是管理简单,但是相对容易出现单点故障。

5.2 主从模式 (+哨兵模式)

  主从模式下,可以任意多主多从配置,要求是每个主节点可以有大于等于0个从节点,一个从节点只能有一个主节点。主从模式的目的是为了高可用,Redis的故障恢复(failover)有手动和自动两种方式,标题把哨兵模式括起来就是告诉大家,哨兵模式是可选方案而不是必备方案。但是有自动的情况下没有理由用手动来保证高可用,所以用主从的情况下几乎必用哨兵模式。下面开始配置(主:7000,从:7003 7004,哨兵:7006 7007):

# master   redis.windows.conf配置文件:
port 7000      
# 两个slave  redis.windows.conf配置文件分别:
port 7003    
slaveof 127.0.0.1 7003  port 7004    
slaveof 127.0.0.1 7004  
# 两个哨兵(sentinel),新建text,配置如下后改成.conf
#当前Sentinel服务运行的端口,另一个改成7007
port 7006
#只有一个 Sentinel进程将实例标记为主观下线并不一定会引起实例的自动故障迁移:只有在足够数量的Sentinel都将一个实例标记为主观下线之后,实例才会被标记为客观下线,这时自动故障迁移才会执行
sentinel myid db6466310f6b573d09842178d7a30520e2f2ad93
#Sentinel去监视一个名为mymaster的主redis实例,这个主实例的IP地址为本机地址127.0.0.1,端口号为7000,只有一个 Sentinel进程将实例标记为主观下线并不一定会引起实例的自动故障迁移:只有在足够数量的Sentinel都将一个实例标记为主观下线之后,实例才会被标记为客观下线,这时自动故障迁移才会执行
sentinel monitor master 127.0.0.1 7000 2
#指定了在执行故障转移时,最多可以有多少个从Redis实例在同步新的主实例,在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长
sentinel down-after-milliseconds master 5000
#如果在该时间(ms)内未能完成failover操作,则认为该failover失败
sentinel config-epoch master 30
# Generated by CONFIG REWRITE,改成你自己的随便哪个路径
dir "D:\\App\\Redis\\cmd"
sentinel leader-epoch master 30
protected-mode yes
bind 127.0.0.1
sentinel current-epoch 30

    启动主从的命令可以放到新建.bat里面,注意路径改成你自己的:

title redis-7000
D:\App\Redis-3.2.100\redis-server D:\App\Redis\Redis-7000\redis.windows.conf

  启动哨兵的命令:

title redis-7006
D:\App\Redis-3.2.100\redis-server D:\App\Redis\Redis-7006\sentinel.conf --sentinel

这时可以测试主从复制是否有效,可以关掉主服务器,观察哨的failover过程。
  哨兵(Sentinel)是一个运行在特殊模式下的Redis实例,可以有多个哨兵实例,它们记录了主从节点信息,当主节点挂掉之后,哨兵们协商出一个领头哨兵,领头哨兵负责故障转移。
  故障转移:领头哨兵负从挂掉节点的从服务器中选出一个作为新的主节点,若挂掉的主节点恢复,哨兵会把它安排为从节点,从新的主节点复制数据。
  哨兵模式主观下线、客观下线:主观下线是指哨兵第一次发现某节点没有响应;客观下线是指发现主观下线的哨兵向其他哨兵询问,如果它们观测到的也是下线状态,那该节点就变成客观下线,进入故障转移流程。

5.3 集群模式

  Redis集群采用无中心结构,集群中的组成部分:主节点、从节点。
  Redis集群的槽是一个分片,一个Redis集群必须有16384个槽,每个主节点映射一部分槽(不要求平分),key-value插入时会用CRC16计算key的校验码,校验码%16384 =它应该保存的槽。
  集群的主节点之间相互保存其它主节点的连接信息、槽信息,向集群中存取数据可以向任意一个主节点发送命令,如果数据不在自己的槽范围,请求会自动跳转到正确的节点。
注意:集群模式需要用Ruby脚本构建,请先安装好(下载地址)。集群模式自带failover,不需要哨兵。集群模式的主从配置完全一致,从库不需要什么slaveof配置(主:7000 7001 7002 从:7003 7004 7005):

# 主从配置都一样:
port  7000
appendfilename "appendonly.7000.aof"  
cluster-enabled yes                                    
cluster-config-file nodes.7000.conf
cluster-node-timeout 15000
cluster-slave-validity-factor 10
cluster-migration-barrier 1
cluster-require-full-coverage yes

上面只是配置,构建集群需要Ruby环境+官网的redis-trib.rb脚本,用下面的命令构建脚本:

# 说明:--replicas表示集群模式下的主从配置,--replicas 1表示每个主1个从,这里3个master,就必须有3个slave,不能多不能少。
# 下面的主从对应关系是 M7000S7003,M7001S7004,M7002S7005
redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

中途需要输入yes同意构建。完成之后用下面的命令检查集群情况:

# 可以向任意节点check集群状态
redis-trib.rb check 127.0.0.1:7001

在这里插入图片描述
  这个时候我们可以测试主从复制情况:

#说明:必须带-c参数,否则7000这个控制台只处理自己的槽范围的命令,加上之后则可能会请求跳转到其它节点D:\App\Redis\Redis-7000\redis-cli -c -p 7000

  也可以测试failover,停掉7000,7003会变成master,7000重启后变成slave。
  故障转移:集群超过一半的master节点发现7000下线,会在集群广播一条7000下线的消息,这些master会从7000的从节点选择一个作为新的作为主节点(这里是7003),7003接管原来7000的槽,7003上线后广播一条消息,新的主节点开始工作。(《Redis设计与实现第17章第6节》)。

6 总结

  实际上Redis并非绝对的单线程,只不过它在接收用户命令、处理并响应时是单线程,RDB save命令、AOF重写时会fork出一个子线程协助处理,都属于多线程;另外我们可以看到Redis为了加快客户端响应速度做出的许多努力:多线程持久化、优先处理文件事件等。本文总结了Redis的底层数据结构和对象的实现,以及在此基础上的一些特性原理,没有总结更高层次事务实现、统计计数等功能,因为要从底层说清楚高层次的东西是很有挑战性的,完整总结出来可能会变成一本厚厚的资料书,希望本文能够用浅显的方式引领看官更深入的学习Redis底层原理。Redis的使用参考我的另一篇笔记Redis操作指南。


http://chatgpt.dhexx.cn/article/Y88HV52O.shtml

相关文章

Redis设计与实现

文章目录 第一部分:内部数据结构简单动态字符串(simple dynamic string)双端链表字典跳跃表 第二部分:内存映射数据结构整数集合intset压缩列表 redis数据类型对象处理机制(redisObject)字符串string哈希表hash列表list集合set有续集zset 第四部分&#…

redis的设计与实现

redis的设计和实现 第一部分、数据结构与对象 一、简单动态字符串: 在大多数情况下redis只会使用c字符串作为字面量,在大多情况下,redis使用SDS作为字符串表示。 比起C字符串,SDS具有五种优点: SDS结构里面会有一…

虚拟IP注册Nacos的问题

虚拟IP注册Nacos的问题 问题: A服务器有两个网卡,网卡 lo 绑定了 127.0.0.1 和一个虚拟IP,网卡 eth0 绑定了本地公网IP和一个虚拟IP。同样B服务器的网卡也是相同的配置,A、B服务器拥有的虚拟IP都是同一个地址。 当将A、B服务器部…

天翼云高可用虚拟IP(HAVIP)实践

产品概述 天翼云高可用虚拟IP(High-Availability Virtual IP Address,简称HAVIP)是一种可用独立创建和删除的私有网络IP地址资源。通过在VIP CIDR中申请一个私有网络IP地址,然后与高可用软件(如高可用软件Keepalived&…

云服务器虚拟ip绑定主机,如何在云平台上给云主机中的Keepalived的虚拟IP绑定弹性IP?...

1、 查看Keepalived和网卡配置文件中虚拟IP地址 查看虚拟机keepalived.config配置文件可以看到本地IP地址为172.16.100.109,虚拟IP地址为172.16.100.104。 (图1 Keepalived配置文件) 查看虚拟机网卡的IP地址情况,可以看到本地IP和虚拟IP。 (图2 查看虚拟…

EasyConnect虚拟IP地址未分配

工作中遇到EasyConnect虚拟IP地址未分配,导致无法正常连接服务器进行调测工作。 检查是否安装成功

蒲公英联机平台的服务器虚拟IP,蒲公英客户端如何使用固定虚拟IP管理虚拟局域网的步骤是什么?...

蒲公英异地组网分为路由器成员与客户端成员两种。其中路由器成员下的电脑,可通过本地连接获取的局域网IP进行组网通信访问;而安装并登录了蒲公英客户端成员,则是通过系统随机分配的临时虚拟IP,来进行组网成员的通讯。当成员移除原…

服务器怎么做虚拟ip,如何在服务器上添加虚拟IP?看完原来如此简单!!

写在前面最近,有位小伙伴为了实现Nginx的高可用,在自己的服务器上搭建了一套Nginx集群,Nginx节点的服务器总共有3台。那么问题来了:如何对外只使用一个IP地址,通过某种策略来访问三个服务器节点上的Nginx?答…

LNMP详解(九)——Nginx虚拟IP实战

今天继续给大家介绍Linux运维的相关知识,本文主要内容是Nginx的虚拟IP实战。 一、实战背景 在LNMP详解(七)——Nginx反向代理配置实战一文中,我们实现了如下所示的架构: 在该架构中,Nginx作为反向代理&a…

centos7 配置虚拟ip

环境概览 master:192.168.46.26 slave1:192.168.46.27 测试机:192.168.46.22(用于ping机器) 安装keepalived yum install -y keepalived修改master keepalived.conf 配置文件 vim /etc/keepalived/keepalived.confi…

计算机 修改 虚拟ip,怎么样在电脑中设置虚拟IP地址?

满意答案 wtc6981 2020.03.01 采纳率:56% 等级:9 已帮助:114人 更改IP地址 广域IP: 1、如果是PPOE上网只需断开连接再重新连上就好了,服务器会从IP地址池中随机分配一个IP地址给你。 2、固定IP上网那你要找运营商更改了,这样改是快不了的。…

虚拟服务器的真实ip,虚拟ip和真实ip区别(图文)

【导读】虚拟ip和真实ip区别,下面就是191路由网整理的网络知识百科,来看看吧! 大家好,我是191路由器网小编,上述问题将由我为大家讲解。 虚拟ip和真实ip区别是真实IP是网络运营商提供的所以不能自己变更,虚…

keepalived配置虚拟IP

YUM安装 # yum安装 yum -y install keepalived # 查看安装版本 rpm -qa keepalived # 查看安装路径 rpm -ql keepalived或是使用源码安装 到这里下载 https://www.keepalived.org/download.html # 安装依赖 yum -y install gcc openssl-devel libnfnetlink-devel 下载源码包…

虚拟ip的概念

1.虚拟IP是什么? 要是单讲解虚拟 IP,理解起来很困难,所以干脆把 动态 IP 、固定 IP 、实体 IP 与虚拟 IP都讲解一下,加深理解和知识扩展 实体 IP:在网络的世界里,为了要辨识每一部计算机的位置,因此有了计算机 IP 位址的定义。一…

虚拟IP简介

什么是虚拟IP 虚拟IP(Virtual IP Address,简称VIP)是一个未分配给真实弹性云服务器网卡的IP地址。弹性云服务器除了拥有私有IP地址外,还可以拥有虚拟IP地址,用户可以通过其中任意一个IP(私有IP/虚拟IP&…

浮点数的表示及其运算

目录 浮点数一般表示 科学计数法 浮点数一般表示 浮点数表示范围 浮点数规格化 IEEE 754 浮点数一般表示 科学计数法 科学记数法的形式是由两个数的乘积组成的。表示为a10^b,例如电子质量9 x 10^28kg 浮点数一般表示 对于浮点数也是类似 ,S:正…

CPU算力(cpu理论浮点运算值)

最近因碰到CPU的算力是多少?所以研究了一下CPU的浮点计算理论值,做个笔记 FLOPS,即每秒浮点运算次数, 是每秒所执行的浮点运算次数(Floating-point operations per second;缩写:FLOPS)的简称&a…

CPU和GPU浮点运算方法-公式

处理器和GPU的计算能力如何计算? (一) CPU的浮点计算性能公式 我们常用双精度浮点运算能力衡量一个处理器的科学计算的能力,就是处理64bit小数点浮动数据的能力 intel的最新cpu支持高级矢量指令集AVX2、AVX512, 其中…

定点运算,浮点运算,算术逻辑单元

定点运算 (一)移位运算 1、移位运算的数学意义 先举一个例子:15m 1500 cm,在这个变换过程中,就可以通过移位运算进行实现,实际上在这个等式中,小数点被隐含了,在15m和1500cm数值最…

计算机组成原理之浮点运算

总结一下自己的学习过程,如果有错误的地方,希望你们可以不吝赐教哦♥ 浮点运算中数的形式 如: 浮点运算中补码的表示形式 如: 浮点运算的步骤 1.求阶差,对阶 对阶时要遵循小阶向大阶看齐的原则 2.尾数求和 3.规格化…