Redis设计与实现笔记

一、数据结构与对象

1、SDS(动态字符串)

  • 主要用于字符串值和AOF(将内存数据存到文件系统,以便下次重启使用)缓冲区
  • 结构包含len、free、buf。len表示当前大小、free表示剩余大小,buff为内容。
  • c字符串再增长和缩短操作都会内存重分配,需要系统调用,耗时。
  • 分配原则:修改后长度小于1M,多分配和len同样大小的未使用空间;大于时多分配1M空间。使得连续增长n次字符串所需的内存重分配次数从必定n次到至多n次。
  • 惰性空间释放:缩短时不会重分配内存。提供api手动释放。
  • 二进制安全,可存储多个空字符,且末尾为空字符。

2、链表

  • 服务器用链表存储客户端状态信息,输出缓冲区。
  • 多态:链表节点用void*指针,可以绑定相关节点操作。

3、字典

  • key-value型。redis数据库就是字典实现。
  • 结构包括两个hash表,trehashidx。hash表的元素时一个结构指针,保存着一个键值对,该结构还有next用开链法以解决冲突,类似stl的hash表。
  • hash表中的sizemask用于hash函数,大小为size - 1,作用为取余。
  • 两个hash主要用于rehash,从一个复制到另一个。
  • trehashidx用于指示渐进式hash。渐进式hash过程中操作会在两个hash表上进行。
  • 扩张与收缩:执行BGSAVE和BGREWRITEAOF命令时,负载因子阈值为5,超过了就要rehash;未执行时为1。因为执行命令时会创建子进程,操作系统采用写时复制,尽可能避免再子进程存在期间进行扩展操作,避免不必要的内存写入。

4、跳表

  • 用于有序集合键、集群节点的内部结构。
  • 节点数据结构:1、层:level数组含了每一层的前进指针和跨度。前进指针用于访问表尾的其他节点,跨度记录了指向节点和当前节点的距离。新创建节点时根据幂次定律生成level数组大小。跨度时计算排位的,所有层跨度累积起来的结果就是排位。2、分值:分值可以相同,成员对象是唯一的,按字典序排列。3、成员对象。4、后退指针。
  • 跳表除了节点还有头尾指针和size。

5、整数集合

  • 集合键的底层实现之一,集合只包含整数元素,且数量不多时用。
  • 包括contents数组(8位)、length、encoding(包括16、32、64位)数组真正类型取决于编码属性的值。
  • 当新元素加入且长度比现有的都大,需要进行升级,把所有的元素都变成大的类型。
  • 不支持降级操作,一直维持升级后的状态。

6、压缩链表

  • 列表键和哈希键的底层实现之一,小整数和长度短的字符串使用。
  • 包括长度、尾节点偏移、节点数,0XFF标记末端等。
  • 节点可以保存一个字节数组或者一个整数值。
  • 节点包括previous_entry_length,表示上一个节点长度,用于回退。encoding表示编码,最高位以11开头为整数,否则为字节数组。content表示实际元素。
  • previous_entry_length的大小根据前一字节长度来定。所以插入或删除元素时会导致重新分配大小,而这会导致节点大小改变而影响下个节点的previous_entry_length,导致连锁更新。最坏复杂度位O(n)。

7、对象

  • redis没有用直接的数据结构实现数据库,而是基于数据结构创建对象系统。
  • 对象结构包括type、encoding、ptr属性。
  • 数据库包括键对象和值对象。键对象总是字符串对象,值对象可以是字符串、列表、哈希、集合、有序结合对象的其中一种。TYPE命令可以看值对象类型。
  • 编码主要是底层数据结构,使用OBJEDT ENCODING查看编码。根据不同场景为同一个对象设置不同的编码。
  • 字符串对象:小的整数值用long类型保存入ptr;小于32字节的用embstr编码方式;大于32的用raw保存;浮点数也是用字符串对象保存的。
  • embstr和raw的区别是用一次内存分配一块连续空间包括object和sdshdr。embstr是只读的,修改之后会自动变成raw编码。
  • 列表对象:编码可以是压缩链表或双向链表,根据元素长度和元素个数定。双向链表的内容为字符串对象。
  • 哈希对象:可以是压缩链表或哈希表。如果是压缩链表,根据键和值一次推入链表尾。哈希表用字典实现,键和值都是字符串对象。
  • 集合对象:可以是整数集合或哈希表。
  • 有序集合对象:可以是压缩链表或跳表。压缩链表保存成员和分值并按分值排序。跳表结构冗余了字典,使排序和查询的时间复杂度都是O(1)。字典和跳表用指针共享成员和分值以节约内存。
  • 执行命令前会进行类型检查,检查输入的值对象和类型是否匹配。
  • 多态:基于类型,一个命令可以同时处理多个不同类型的键;基于编码,一个命令可以处理多种不同的编码。
  • redis通过计数实现内存回收和对象共享。 尽管共享复杂的对象可以节约更多内存,但受CPU时间限制,redis只对包含整数的字符串对象进行共享,因为复杂共享对象验证目标对象和共享对象是否相同的时间复杂度越高。
  • 空转时长:记录最后一次被程序访问的时间,用于lru操作。

二、单机数据库的实现

1、数据库

  • 服务器所有数据库存在redisServer结构的db数组,每一项是一个数据库的结构redisDb。客户端会有一个指向数据库结构的指针指向服务器中的数据库。
  • redisDb结构的dict字典保存数据库中的所有键值对,称为键空间。通过过期字典为键设置生存时间。
  • 过期键删除策略:定时删除;惰性删除(使用时检查);定期删除。定时删除对CPU不友好,惰性删除对内存也不友好,定期是一种折衷,难点在于确定删除的时长和频率。定期删除随机从一定数据库取出一定数量的键检查并删除过期键,设置全局变量保存当前数据库。
  • 过期键不会写入RDB,AOF则加入DEL命令。
  • 从服务器不会将过期键删除,当作未过期处理。知道主服务器发来DEL命令。
  • 数据库通知:让用户获知数据库键的变化。

2、RDB持久化

  • Redis是内存数据库,进程退出状态会消失。需要将数据库状态写入磁盘里,避免数据意外丢失。
  • SAVE会阻塞服务器进程,知道文件创建为止。BGSAVE用子进程处理。两者不能同时进行,防止竞争条件。BGWRITEAOF也是用子进程,并没有冲突,但是会执行磁盘写入,影响性能,因此也不能同时进行。
  • 可以设定保存的条件,主要体现为数据库多少时间内修改次数。通过dirty计数器和lastsave保存上次时间实现。
  • RDB优先级没有AOF高。
  • RDB文件结构:REDIS字符、版本、数据库、AOF、校验。
  • RDB中数据库结构:SELECTDB、数据库号码、键值对。键值对包括类型、key、value,主要存储形式为压缩链表,包括字符串长度和字符串。有过期时间的键值对前面还多出过期时间type和过期时间。

3、AOF持久化

  • AOF通过保存写命令记录数据库状态,服务器创建为客户端读入文件的写命令并执行,就可以还原。可分为追加、文件写入、文件同步操作。
  • 服务器执行写命令后会把写命令追加到服务器状态的aof_buf缓冲区末尾。服务器结束每一个事件循环前胡回考虑是否将缓冲区内容写入文件。包括always、everysec、no。决定写入和同步时间。同步指的是内核缓冲去的flush操作。
  • 为解决AOF体积膨胀,提供了重写功能,直接从数据库读取内容生成对应的命令,在子进程中执行。
  • 问了防止重写过程中状态变化的不一致问题,设置AOF重写缓冲区,在创建子进程之后使用,执行写命令时也往改缓冲区写入。完成后向父进程发送信号,父进程的信号处理函数把重写缓冲区的内容写入到新AOF文件中,并覆盖原文件。
  • PUBSUB、SCRIPT LOAD等参数虽然没有对数据库进行修改,但是对客户端/服务端的状态发生改变,因此也要写入AOF文件。

4、事件

  • 主要包括文件事件和时间事件。文件事件框架为单线程+IO多路复用,封装了select、epoll、evport、kqueue等。
  • IO多路复用优先处理读事件。
  • 时间事件分定时和定期两种。定期主要是通过每次在时间到达后,更新下一次事件实现。需要遍历链表查找到达的时间事件。正常的服务器只用serverCron一个时间事件,不影响性能。
  • serverCron主要是清理过期键值对、关闭失效客户端,持久化操作,同步和连接测试。
  • 通过把最近的时间事件的剩余时间设置为epoll的超时时间来分配文件和时间事件。

5、客户端

  • 每个客户端有相应的redisClient结构,服务器状态有一个redisClient链表。
  • 结构中的fd为-1时为伪客户端,可以为客户端设置名字。flag表示客户端的状态。
  • 每个客户端还有输入输出缓冲区,输出缓冲区超过1G会关闭客户端。保存到querbuf(输入缓冲区)之后会进行解析,得到命令、命令参数以及参数个数,存入argv和argc。接下来会查命令表得到实现函数。
  • 输出缓冲区包括固定大小缓冲和可变大小缓冲。
  • authenticated记录客户是否通过身份验证,未验证的客户只能执行AUTH命令。
  • 客户端关闭原因:客户端进程退出,发送不合格式的请求,称为client kill的目标,空转时间超时,缓冲区大小超限。
  • 输出缓冲硬性限制,超过即被关闭;软性限制,超过且持续设定的时间长即关闭。
  • 输入缓冲区大小超出限度时,程序会释放当前缓冲区,重新创建输入缓冲区。

6、服务器

  • 一个命令请求到发送:客户端请求,服务器读取请求分析参数,查找并执行实现函数,得到命令回复,将回复发送到客户端。
  • 命令执行器在执行函数之前会有预备操作,完成后有后续工作。
  • 命令大小写不影响结果。
  • serverCron每100ms执行一次。
  • 服务器启动:初始化服务器状态,载入服务器配置,初始化服务器数据结构,还原数据库状态,执行事件循环。
  • 服务器必须先载入配置选项,才能正确的对数据结构进行初始化。
  • 在执行BGSAVE期间,客户端的BGREWRITEAOF胡会被延期执行,通过保存两个子进程的pid实现,如果为-1说明没有进行,如果进行则后执行的一方调用wait,否则就判断是否进行持久化操作。

三、多机数据库的实现

1、复制

  • 用户可通过SLAVEOD让一个服务器复制另一个服务器,被复制的为主服务器,复制的为从服务器。主从服务器保存相同的数据称为一致。
  • 同步操作用于将从服务器状态更新为主服务器状态;命令传播操作用于在主服务器的数据库被修改时,向从服务器发送写命令使其保证一致。
  • 同步需要SYNC命令完成,收到命令的主服务器生成RDB文件并用缓冲区保存主命令,将两者先后发给从服务器更新。
  • 复制包括完整重同步和部分重同步。完整重复处理初次复制情况,部分重同步用于断线情况,只发送断开之后未同步的命令。
  • 偏移量是主服务器发送的字节数,是从服务器收到的字节数。复制积压缓冲区是写命令缓冲区。部分重同步通过偏移量表示偏差的数据,是一个固定长度队列。如果偏移量之后的数据没保存在复制积压缓冲区,就采用完整重同步。
  • 从服务器会保存主服务器的运行ID,断线后ID如果和新主服务器不同,进行完整重同步。
  • 复制的步骤(从服务器):设置主服务器地址和端口,建立连接,发送PING命令,身份验证,发送端口信息(从服务器自己的信息),同步,命令传播。
  • 心跳检测:从服务器会每秒相助服务器发送命令,包括自己的复制偏移量。作用:检测连接状态,辅助实现min-slaves选项(防止不安全的情况下执行写命令),检测命令丢失(重传)。

2、Sentinel

  • Sentinel可以监视多个主服务器以及属下的从服务器,主服务器下线时可自动将下线服务器属下的从服务器升级为新的主服务器。
  • Sentinel会继续监视已下线的服务器,重新上线时设置为新的主服务器的从服务器。
  • Sentinel不使用数据库,不载入持久化文件,命令码也和普通的不一样。
  • Sentinel的master字典记录被Sentinel监视的主服务器信息。
  • Sentinel会向主服务器建立命令连接和订阅连接。Sentinel之间只有命令连接。
  • Sentinel每10秒发送INFO命令以获得主服务器回复的信息。包括主服务器以及下属从服务器端信息。
  • Sentinel每2秒发送订阅信息,用于多个Sentinel进行信息交互。
  • Sentinel每秒一次想服务器发送PING,判断服务器是否在线,如果在指定的时间内一直没有回复,该Sentinel判断实例已经进入主观下线状态。当Sentinel从其他Sentinel接收到足够多的已下线判断之后,Sentinel就会将其判断为客观下线,并对其进行故障转移操作。
  • 当主服务器被判定为客观下线时,监视这个服务器的Sentinel会进行选举得到领头Sentinel,领头Sentinel对其进行故障转移操作。具体是raft算法,详见p239。
  • 新的主服务器得保证与之前断开主服务器的连接时间比较新,回复Sentinel也比较新的服务器。当被升级服务器的role变为master时,Sentinel就知道已经升级了。

3、集群

  • 集群通过分片进行数据共享,并提供复制和故障转移功能。一个集群由多个节点构成,把各自独立的节点连接起来才能构成包含多个节点的集群。
  • 向一个节点发送CLUSTER MEET命令,可以让其与指定的节点握手。每个节点都会用clusterNode结构记录自己的状态,并且为其他节点也创建结构。clusterState存放clusterNode对应的字典。
  • 集群通过槽进行指派,数据库的所有键都属于某一个槽。所有的槽得到处理之后集群才处于上线状态。clusterNode和clusterState会冗余存放槽的状态,降低时间复杂度。集群中的每个节点都会互相交换和更新slot信息。
  • 接受命令的节点检查键的槽是否属于自己,如果不属于,就会返回MOVED错误让客户端重定向。
  • 节点数据库用跳表存储槽和键的关系。
  • 重新分片操作可以将任意数量已经指派的节点的槽改为指派给另一个节点,将键值对转移。
  • ASK错误:源节点在分片迁移时被访问,没找到指定的键,这个键有可能被迁移到目标节点,就像客户端返回ASK错误。结构中的数组会记录正在迁移或导入的槽。
  • 接收到ASK错误的客户端要发送ASKING命令之后才能重新请求,且只有ASKING状态只有一次,设置标志位,已经接收转移但还没更新的节点看到这个标识位会破例执行槽的命令一次。
  • 集群中也有主从节点,主节点处理槽,从节点做主节点的备份,主节点下线时替换。
  • 主从节点有类似Sentinel的故障操作和raft选举算法。

四、独立功能实现

1、发布与订阅

  • 执行SUBSCRIBE命令的客户端可订阅一个或多个频道,成为频道的订阅者,任何向这个频道发送的消息都可以被接收到。
  • 执行PSUBSCRIBE命令的客户端可订阅一个或多个模式,成为模式的订阅者,任何向与频道匹配的模式发送的消息都可以被接收到。
  • 所有频道的订阅关系保存在服务器的pubsub_channels字典里,键是频道,值是订阅这个频道的客户端。
  • 模式是一个链表,元素包括订阅的模式和客户端。
  • 向频道发送消息时要把消息发给所有订阅者和模式匹配的订阅者。

2、Lua脚本

  • 主要是解析常用的命令,使用Lua环境生成临时函数并执行。

3、排序

4、二进制位数组

  • 这里用的字符串对象表示位数组。

5、慢查询日志

  • 用于记录执行时间超过给定时长的命令请求,用户可以通过这个功能产生的日志来监视和优化查询结果。
  • 主要用时间戳实现。慢查询日志有长度,先进先出。

6、监视器

  • 通过MONITOR命令,客户端可以将自己变成一个监视器
    实时接收并打印服务器当前处理的命令请求相关信息。
    通过链表保存监视的客户端,处理命令前遍历所有的客户端发送。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值