Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(下)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis源码、面试指南(4)单机数据库、持久化、通知与订阅

Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(中):https://developer.aliyun.com/article/1508243

事件类型

ae.h中定义了IO多路复用程序所监听的两类事件,AE_READABLE和AE_WEUTABLE:

·READABLE事件,当套接字可读(客户端write或close或新的可应答套接字)时产生;

·WRITABLE事件,套接字可写时,如客户端执行read;

当一个套接字同时产生了两类事件,即可读又可写,那么优先执行度再写;(为什么呢?可能是为了防止读操作被覆盖)

时间事件

Redis中的时间事件主要分为两类,一是定时事件(返回AE_NOMORE),二是周期性事件(返回非AE_NOMORE整数值);

时间事件由三个属性构成:

·id:全局唯一ID,递增;

·when:unix时间戳,记录预设时间;

·timeProc:时间事件处理器,类似于回调函数,当时间事件到达时,就会调用相应的处理器进行处理;

Redis的时间事件是基于一个无序(指when无序,而不是id无序)链表实现的新来的放在表头,当时间事件执行器运行时,遍历整个链表,找出已到达的时间事件,并调用相应的事件处理器(这跟poll、select一样,效率很低?在3.0版本下,正常模式的Redis服务器仅有serverCron时间事件,benchmark模式下也只有两个,几乎不影响性能)。

事件的调度与执行

Redis对时间和文件事件的调度策略如下图所示:


客户端

Redis服务器可以与多个客户端建立连接,每个客户端连接时,服务器都保存了相应状态,如下:

其中每个客户端都具有自己的属性。

基本属性

typedef struct redisClient {
    // 套接字描述符
    int fd;
    // 当前正在使用的数据库
    redisDb *db;
    // 当前正在使用的数据库的 id (号码)
    int dictid;
    // 客户端的名字
    robj *name;             
    // 查询缓冲区
    sds querybuf;
    // 查询缓冲区长度峰值
    size_t querybuf_peak;   
    // 参数数量
    int argc;
    // 参数对象数组
    robj **argv;
    // 记录被客户端执行的命令
    struct redisCommand *cmd, *lastcmd;
    // 请求的类型:内联命令还是多条命令
    int reqtype;
    // 剩余未读取的命令内容数量
    int multibulklen;       
    // 命令内容的长度
    long bulklen;           
    // 回复链表
    list *reply;
    // 回复链表中对象的总大小
    unsigned long reply_bytes; 

    // 已发送字节,处理 short write 用
    int sentlen;                // 创建客户端的时间
    time_t ctime;           /* Client creation time */

    // 客户端最后一次和服务器互动的时间
    time_t lastinteraction; 

    // 客户端的输出缓冲区超过软性限制的时间
    time_t obuf_soft_limit_reached_time;

    // 客户端状态标志
    int flags;              /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */

    // 当 server.requirepass 不为 NULL 时
    // 代表认证的状态
    // 0 代表未认证, 1 代表已认证
    int authenticated;      

    // 复制状态
    int replstate;          
    // 用于保存主服务器传来的 RDB 文件的文件描述符
    int repldbfd;           /* replication DB file descriptor */

    // 读取主服务器传来的 RDB 文件的偏移量
    off_t repldboff;        /* replication DB file offset */
    // 主服务器传来的 RDB 文件的大小
    off_t repldbsize;       /* replication DB file size */

    sds replpreamble;       /* replication DB preamble. */

    // 主服务器的复制偏移量
    long long reploff;      /* replication offset if this is our master */
    // 从服务器最后一次发送 REPLCONF ACK 时的偏移量
    long long repl_ack_off; /* replication ack offset, if this is a slave */
    // 从服务器最后一次发送 REPLCONF ACK 的时间
    long long repl_ack_time;/* replication ack time, if this is a slave */
    // 主服务器的 master run ID
    // 保存在客户端,用于执行部分重同步
    char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
    // 从服务器的监听端口号
    int slave_listening_port; /* As configured with: SLAVECONF listening-port */

    // 事务状态
    multiState mstate;      /* MULTI/EXEC state */
    // 阻塞类型
    int btype;              /* Type of blocking op if REDIS_BLOCKED. */
    // 阻塞状态
    blockingState bpop;     /* blocking state */
    // 最后被写入的全局复制偏移量
    long long woff;         /* Last write global replication offset. */
    // 被监视的键
    list *watched_keys;     /* Keys WATCHED for MULTI/EXEC CAS */
    // 这个字典记录了客户端所有订阅的频道
    // 键为频道名字,值为 NULL
    // 也即是,一个频道的集合
    dict *pubsub_channels;  /* channels a client is interested in (SUBSCRIBE) */
    // 链表,包含多个 pubsubPattern 结构
    // 记录了所有订阅频道的客户端的信息
    // 新 pubsubPattern 结构总是被添加到表尾
    list *pubsub_patterns;  /* patterns a client is interested in (SUBSCRIBE) */
    sds peerid;             /* Cached peer ID. */

    // 回复偏移量
    int bufpos;
    // 回复缓冲区
    char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient;

主要围绕**其中几个重要属性(从上至下)**进行分析:

·套接字描述符fd:取值为-1(伪客户端,处理的命令请求来自AOF文件或者LUA脚本)或大于-1(普通客户端);

client list 可以列出当前服务器的客户端及其所使用的描述符等;

·name:默认为空,可以通过client setname命令设置;

·flags标志:记录客户端的角色及其状态,即可以是单个标志,也可以是多个标志的或:

每一个标志都是一个常量,查看redis.h:

* Client flags */
#define REDIS_SLAVE (1<<0)   /* This client is a slave server */
#define REDIS_MASTER (1<<1)  /* This client is a master server */
#define REDIS_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */
#define REDIS_MULTI (1<<3)   /* This client is in a MULTI context */
#define REDIS_BLOCKED (1<<4) /* The client is waiting in a blocking operation */
#define REDIS_DIRTY_CAS (1<<5) /* Watched keys modified. EXEC will fail. */
#define REDIS_CLOSE_AFTER_REPLY (1<<6) /* Close after writing entire reply. */
#define REDIS_UNBLOCKED (1<<7) /* This client was unblocked and is stored in
                                  server.unblocked_clients */
#define REDIS_LUA_CLIENT (1<<8) /* This is a non connected client used by Lua */
#define REDIS_ASKING (1<<9)     /* Client issued the ASKING command */
#define REDIS_CLOSE_ASAP (1<<10)/* Close this client ASAP */
#define REDIS_UNIX_SOCKET (1<<11) /* Client connected via Unix domain socket */
#define REDIS_DIRTY_EXEC (1<<12)  /* EXEC will fail for errors while queueing */
#define REDIS_MASTER_FORCE_REPLY (1<<13)  /* Queue replies even if is master */
#define REDIS_FORCE_AOF (1<<14)   /* Force AOF propagation of current cmd. */
#define REDIS_FORCE_REPL (1<<15)  /* Force replication of current cmd. */
#define REDIS_PRE_PSYNC (1<<16)   /* Instance don't understand PSYNC. */
#define REDIS_READONLY (1<<17)    /* Cluster client is in read-only state. */

·argv、argc命令与命令参数:即是客户端发送给服务器的命令,实例如下:

那么服务器在接收命令之后,如何查找命令的实现函数呢?

·cmd命令实现函数:这个步骤是通过一个Dict结构的命令表实现的:

当在命令表中找到argv[0]所对应的command结构时,cmd就会指向该结构。而后服务器就可以使用cmd以及argv、argc保存的属性来执行相应的函数。

·输出缓冲区,有两个缓冲区,一个大小固定,一个大小可变

大小固定保存长度较小的回复,如ok、简单字符串值、整数值等等,主要由以下两个参数组成:

可变大小的缓冲区保存如非常大(如果固定大小不够用了,或者放不下,就会使用它)的字符串、列表、集合等,其结构是一个链表List* reply:


·身份验证authenticated:表示客户端是否通过了身份验证,0表示未验证,1表示验证。

当为0时,客户端的请求命令除了AUTH之外都会被拒绝。

创建与关闭

服务器启动会创建负责执行Lua脚本的伪客户端(服务器生命周期内一直存在)、执行AOF中redis命令的伪客户端(载入完毕之后关闭)。

除此之外,当使用网络连接到服务器时,会创建普通客户端,普通客户端可能由于发送不合协议格式的命令而被关闭。

服务器

命令请求执行过程

若客户端执行以下命令:set key value,那么从输入该命令到客户端回复ok,需要进行以下操作:

接下来分别介绍这四个部分:

·发送命令请求

首先客户端会将命令请求转换成协议格式,然后通过套接字将该请求数据发送给服务器:

·读取命令请求

当服务器获取到某客户端的套接字可读时,它就会调用命令请求处理其器来执行以下操作:

·命令执行器

分为四个部分:查找命令实现(从命令表中查找所指定的命令结构)、执行预备操作(检查命令是否找到、参数是否合法、客户端是否已验证、是否正在执行事务等等,简而言之,就是看现在是不是执行该命令的时机)、调用命令的实现函数执行后续操作(日志、更新calls属性、广播给其他服务器等等)

命令表Dict中存储的是Redis命令的结构,如下:

/*
 * Redis 命令
 */
struct redisCommand {
    // 命令名字
    char *name;
    // 实现函数
    redisCommandProc *proc;
    // 参数个数
    int arity;
    // 字符串表示的 FLAG
    char *sflags; /* Flags as string representation, one char per flag. */
    // 实际 FLAG
    int flags;    /* The actual flags, obtained from the 'sflags' field. */
    /* Use a function to determine keys arguments in a command line.
     * Used for Redis Cluster redirect. */
    // 从命令中判断命令的键参数。在 Redis 集群转向时使用。
    redisGetKeysProc *getkeys_proc;
    /* What keys should be loaded in background when calling this command? */
    // 指定哪些参数是 key
    int firstkey; /* The first argument that's a key (0 = no keys) */
    int lastkey;  /* The last argument that's a key */
    int keystep;  /* The step between first and last key */
    // 统计信息
    // microseconds 记录了命令执行耗费的总毫微秒数
    // calls 是命令被执行的总次数
    long long microseconds, calls;
};

注:redis命令不区分大小写;

·命令回复发送给客户端:命令实现函数会将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字关联命令回复处理器,当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端

当命令回复发送完毕之后,回复处理器会清空客户端状态的输出缓冲区为处理下一个命令做准备

·客户端接收并打印:如下


serverCron函数

源码地址redis.c/serverCron,1300+行左右位置。

该函数**默认100毫秒(可配置)**执行一次,负责管理服务器资源并保持服务器自身的良好运转

建议结合源码及下图理解其功能:


实际 FLAG

int flags; /* The actual flags, obtained from the ‘sflags’ field. /

/ Use a function to determine keys arguments in a command line.

* Used for Redis Cluster redirect. */

// 从命令中判断命令的键参数。在 Redis 集群转向时使用。

redisGetKeysProc getkeys_proc;

/ What keys should be loaded in background when calling this command? /

// 指定哪些参数是 key

int firstkey; / The first argument that’s a key (0 = no keys) /

int lastkey; / The last argument that’s a key /

int keystep; / The step between first and last key */

// 统计信息

// microseconds 记录了命令执行耗费的总毫微秒数

// calls 是命令被执行的总次数

long long microseconds, calls;

};






注:redis命令不区分大小写;

·命令回复发送给客户端:命令实现函数会将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字关联命令回复处理器,**当客户端套接字变为可写状态时,服务器就会执行命令回复处理器**,将保存在客户端**输出缓冲区中的命令回复发送给客户端**。

当命令回复发送完毕之后,**回复处理器会清空客户端状态的输出缓冲区为处理下一个命令做准备**。

·客户端接收并打印:如下

​        [外链图片转存中...(img-BvmHgQDJ-1618293129983)]        

### **serverCron函数**

源码地址redis.c/serverCron,1300+行左右位置。

该函数**默认100毫秒(可配置)**执行一次,**负责管理服务器资源**,**并保持服务器自身的良好运转**。

建议结合源码及下图理解其功能:

​        [外链图片转存中...(img-dtyYnj3w-1618293129984)]        

​        ![img](https://ucc.alicdn.com/images/user-upload-01/img_convert/01c26db1180d61ab41c3c6d81f68db37.png)        
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
26天前
|
存储 缓存 NoSQL
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
35 2
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
|
28天前
|
存储 缓存 API
LangChain-18 Caching 将回答内容进行缓存 可在内存中或数据库中持久化缓存
LangChain-18 Caching 将回答内容进行缓存 可在内存中或数据库中持久化缓存
39 6
|
26天前
|
SQL 分布式计算 NoSQL
大数据-42 Redis 功能扩展 发布/订阅模式 事务相关的内容 Redis弱事务
大数据-42 Redis 功能扩展 发布/订阅模式 事务相关的内容 Redis弱事务
22 2
|
26天前
|
消息中间件 分布式计算 NoSQL
大数据-41 Redis 类型集合(2) bitmap位操作 geohash空间计算 stream持久化消息队列 Z阶曲线 Base32编码
大数据-41 Redis 类型集合(2) bitmap位操作 geohash空间计算 stream持久化消息队列 Z阶曲线 Base32编码
23 2
|
26天前
|
存储 缓存 NoSQL
大数据-46 Redis 持久化 RDB AOF 配置参数 混合模式 具体原理 触发方式 优点与缺点
大数据-46 Redis 持久化 RDB AOF 配置参数 混合模式 具体原理 触发方式 优点与缺点
55 1
|
2月前
|
存储 缓存 NoSQL
Redis 大 Key 对持久化的影响及解决方案
Redis 大 Key 对持久化的影响及解决方案
42 1
|
2月前
|
存储 NoSQL 安全
8)详解 Redis 的配置文件以及数据持久化
8)详解 Redis 的配置文件以及数据持久化
31 0
|
19天前
|
存储 关系型数据库 MySQL
Mysql(4)—数据库索引
数据库索引是用于提高数据检索效率的数据结构,类似于书籍中的索引。它允许用户快速找到数据,而无需扫描整个表。MySQL中的索引可以显著提升查询速度,使数据库操作更加高效。索引的发展经历了从无索引、简单索引到B-树、哈希索引、位图索引、全文索引等多个阶段。
54 3
Mysql(4)—数据库索引
|
4天前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。同时,文章还对比了编译源码安装与使用 RPM 包安装的优缺点,帮助读者根据需求选择最合适的方法。通过具体案例,展示了编译源码安装的灵活性和定制性。
29 2
|
7天前
|
存储 关系型数据库 MySQL
MySQL vs. PostgreSQL:选择适合你的开源数据库
在众多开源数据库中,MySQL和PostgreSQL无疑是最受欢迎的两个。它们都有着强大的功能、广泛的社区支持和丰富的生态系统。然而,它们在设计理念、性能特点、功能特性等方面存在着显著的差异。本文将从这三个方面对MySQL和PostgreSQL进行比较,以帮助您选择更适合您需求的开源数据库。
32 4