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
相关文章
|
13天前
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
14天前
|
存储 NoSQL Redis
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
Redis持久化、RDB和AOF方案、Redis主从集群、哨兵、分片集群、散列插槽、自动手动故障转移
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
|
1月前
|
存储 缓存 NoSQL
Redis内存管理揭秘:掌握淘汰策略,让你的数据库在高并发下也能游刃有余,守护业务稳定运行!
【8月更文挑战第22天】Redis的内存淘汰策略管理内存使用,防止溢出。主要包括:noeviction(拒绝新写入)、LRU/LFU(淘汰最少使用/最不常用数据)、RANDOM(随机淘汰)及TTL(淘汰接近过期数据)。策略选择需依据应用场景、数据特性和性能需求。可通过Redis命令行工具或配置文件进行设置。
42 2
|
1月前
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。
|
1月前
|
NoSQL Redis
Redis 临时manifest修改问题之确保被持久化到磁盘如何解决
Redis 临时manifest修改问题之确保被持久化到磁盘如何解决
|
1月前
|
网络协议 NoSQL 网络安全
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
【Azure 应用服务】由Web App“无法连接数据库”而逐步分析到解析内网地址的办法(SQL和Redis开启private endpoint,只能通过内网访问,无法从公网访问的情况下)
|
1月前
|
NoSQL Redis
Redis6入门到实战------ 五、Redis的发布和订阅
这篇文章介绍了Redis的发布和订阅机制,包括其基本概念、客户端如何订阅频道以及如何发布消息给订阅者。
|
10天前
|
NoSQL 关系型数据库 MySQL
微服务架构下的数据库选择:MySQL、PostgreSQL 还是 NoSQL?
在微服务架构中,数据库的选择至关重要。不同类型的数据库适用于不同的需求和场景。在本文章中,我们将深入探讨传统的关系型数据库(如 MySQL 和 PostgreSQL)与现代 NoSQL 数据库的优劣势,并分析在微服务架构下的最佳实践。
|
12天前
|
存储 SQL 关系型数据库
使用MySQL Workbench进行数据库备份
【9月更文挑战第13天】以下是使用MySQL Workbench进行数据库备份的步骤:启动软件后,通过“Database”菜单中的“管理连接”选项配置并选择要备份的数据库。随后,选择“数据导出”,确认导出的数据库及格式(推荐SQL格式),设置存储路径,点击“开始导出”。完成后,可在指定路径找到备份文件,建议定期备份并存储于安全位置。
133 11
|
1月前
|
弹性计算 关系型数据库 数据库
手把手带你从自建 MySQL 迁移到云数据库,一步就能脱胎换骨
阿里云瑶池数据库来开课啦!自建数据库迁移至云数据库 RDS原来只要一步操作就能搞定!点击阅读原文完成实验就可获得一本日历哦~