Redis对象
Redis中最核心的数据结构是Redis对象, Redis中几乎所有东西最终都以Redis对象的形式表示, 其定义如下
1 | struct redisObject { |
其中使用位域的方式定义了三个变量, 表示数据类型的type
字段和表示编码类型的encoding
字段公用1字节, 而实现缓存淘汰策略的lru
字段使用剩余的3字节.
缓存淘汰算法最常用的是LRU(记录最近一次访问时间)和LFU(记录访问的次数, 和访问的时间).
refcount
是引用计数, 使得一个redisObject
可以在不同的位置进行共享, 从而避免重复的内存分配行为. ptr
是指向具体数据结构的指针.
数据类型与编码
数据类型的具体定义如下, 其中大部分类型在Redis源码分析笔记之数据结构 | LiZeC的博客中已有介绍, 可查阅此文章进一步了解相关实现的细节.
1 | /* The actual Redis Object */ |
编码类型的具体定义如下
1 |
其中大部分编码类型在之前的文章中也有介绍. 对于某一特定的类型, Redis可能根据其实际存储的数据, 选择不同的编码类型.
这里需要展开说明一下OBJ_ENCODING_EMBSTR
编码类型. 在redisObject
中实际存储的数据通过ptr
访问, 分散存储将导致CPU的缓存命中情况较低, 不利于性能. 因此在OBJ_ENCODING_EMBSTR
模式下, 将会在ptr
指针之后直接分配一个sds
存储对应的数据, 从而使相应的数据在内存中连续分布.
缓存淘汰算法
对于LRU算法, 其实现较为简单, 仅需要比较最近访问时间即可得到分数的高低. 访问时间离当前时刻越近的key得分越高.
对于LFU算法, 其中记录的访问的次数和最近一次访问的时间. 在更新此字段时不可直接对访问次数进行累加, 而需要根据上一次访问时间进行衰减, 使得较长时间未访问的Kye的LFU分数下降. 其算法如下
1 | unsigned long LFUDecrAndReturn(robj *o) { |
基本上可以理解为上次访问到现在经过了多少个
lfu_decay_time
, 那么counter就减少相应的次数
客户端结构体对象
客户端结构体为client
, 其中包含了几十个字段, 以下仅列举一些核心的参数
1 | typedef struct client { |
conn
是表示连接的结构体, 其中存储了该client对应socket的文件描述符, 注册的回调函数等信息. db
是该client使用的数据库指针, Redis默认定义了16个数据库, 因此至少存在16个redisDb
对象.
querybuf
缓存客户端发送的查询信息, 由于TCP可能存在拆分发送, 因此需要一个缓冲区来组装信息. cmd
字段使用redisCommand
记录了当前执行的指令.
其余字段含义比较明确, 可基于注释了解其用途.
redisDb对象
redisDb
对象的结构体如下所示:
1 | typedef struct redisDb { |
其中的大部分字段基于注释都很好理解, 这里需要解释一下watched_keys
字段, 该字段存储了Redis事务中使用WATCH指令关注的KEY, 当对应的KEY发生变更时进行对应的标记, 使得事务执行时能够感知到该变化并拒绝事务执行.
redisCommand对象
redisCommand
可以视为Redis中具体指令的实现函数的封装, 增加了ACL控制属性, 指令的文档信息等内容. 其结构体中部分字段如下
1 | struct redisCommand { |
由于Redis中指令众多, 因此在源码的commands.def
文件中, 通过宏的方式注册了许多指令的信息, 之后通过如下的函数填充到服务端结构体对象之中.
1 | /* Populates the Redis Command Table dict from the static table in commands.c |
服务端结构体对象
服务端结构体为redisServer
, 其中包含了几百个字段, 以下仅列举一些核心的参数
1 | struct redisServer { |
其中el
表示事件循环, Redis服务器是典型的事件驱动程序, 而事件又分为文件事件(socket的可读可写事件)与时间事件(定时任务)两大类. 无论是文件事件还是时间事件都封装在结构体aeEventLoop中:
1 | /* State of an event based program */ |
事件处理机制
Redis的事件处理机制是基于Reactor模式实现的,主要用于处理客户端连接、读写事件以及定时任务等。Redis的事件处理机制主要包括以下几个核心组件:
1. 多路复用器(Multiplexer) 多路复用器负责监听多个文件描述符(例如套接字),并在这些文件描述符上有事件发生时通知Redis。Redis支持多种多路复用器,如epoll
(Linux)、kqueue
(BSD/macOS)和select
(跨平台)。多路复用器的主要优点是可以在单个线程中高效地处理大量并发连接。
2. 事件处理器(Event Handler) 事件处理器负责处理不同类型的事件,如连接建立、数据读取、数据写入等。Redis为每种事件类型定义了一个处理器,例如acceptTcpHandler
用于处理新的TCP连接,readQueryFromClient
用于从客户端读取查询命令等。
3. 事件循环(Event Loop) 事件循环是Redis事件处理机制的核心,它不断地运行并调用多路复用器来检查文件描述符上的事件。当有事件发生时,事件循环会根据事件类型调用相应的事件处理器来处理事件。事件循环会一直运行,直到Redis服务器关闭。
4. 定时任务(Timers) Redis还支持定时任务,例如延迟关闭客户端连接、定期持久化数据等。定时任务由一个独立的定时器管理器负责处理。当定时器触发时,相应的事件处理器会被调用。
以下是Redis事件处理的基本流程:
- 启动Redis服务器时,初始化多路复用器、事件处理器和事件循环。
- 事件循环开始运行,调用多路复用器检查文件描述符上的事件。
- 当有事件发生时,多路复用器会返回一个事件列表,其中包含发生事件的文件描述符和事件类型。
- 事件循环遍历事件列表,根据事件类型调用相应的事件处理器来处理事件。
- 事件处理器处理完事件后,事件循环继续运行,等待下一个事件发生。
- 如果有定时任务触发,事件循环会暂停当前的事件处理,调用相应的事件处理器处理定时任务,然后继续处理其他事件。
通过这种事件处理机制,Redis可以在单线程中高效地处理大量并发连接和各种事件,从而实现高性能和高吞吐量。
Redis指令格式
Redis通信协议(RESP, Redis Serialization Protocol)是Redis客户端与服务器端之间交换数据的一种格式。它是一个简单的基于文本的协议,易于理解和实现。
发送指令
Redis客户端发送给服务器端的指令遵循以下格式:
1 | *<参数数量>\r\n |
*<参数数量>
:表示接下来的参数数量,以星号开头,后面跟参数的数量。$<参数长度>
:表示参数的长度,以美元符号开头,后面跟参数的字节长度。<参数内容>
:参数的实际内容。\r\n
:回车换行符,用于分隔不同的部分。
例如,发送一个SET key value
指令:
1 | *3\r\n |
返回响应
Redis服务器端返回给客户端的响应遵循以下格式:
1 | <响应类型><具体内容> |
响应类型有以下几种:
- 简单字符串(Simple Strings):以
+
开头,表示操作成功或返回一些简单的信息。 - 错误信息(Errors):以
-
开头,表示操作失败或错误信息。 - 整数(Integers):以
:
开头,表示返回一个整数值。 - 批量字符串(Bulk Strings):以
$
开头,表示返回一个字符串。如果长度为-1
,表示返回空值(nil)。 - 数组(Arrays):以
*
开头,表示返回一个数组。数组中的每个元素可以是上述任意一种类型。
例如,对于SET key value
指令的响应:
1 | +OK\r\n |
对于GET key
指令的响应:
1 | $5\r\n |
对于INCR counter
指令的响应:
1 | :1\r\n |
对于LRANGE mylist 0 -1
指令的响应:
1 | *3\r\n |
总之,Redis的RESP协议简单易用,使得客户端和服务器端之间的通信变得高效且易于实现。
最后更新: 2024年12月05日 21:23
版权声明:本文为原创文章,转载请注明出处