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

本文涉及的产品
云数据库 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天前
|
存储 NoSQL Java
【面试宝藏】Redis 常见面试题解析
Redis 是内存数据结构存储系统,用作数据库、缓存和消息中间件,支持字符串、哈希、列表等数据类型。它的优点包括高性能、原子操作、持久化和复制。相比 Memcached,Redis 提供数据持久化、丰富数据结构和发布/订阅功能。Redis 采用单线程模型,但通过 I/O 多路复用处理高并发。常见的面试问题涉及持久化机制、过期键删除、回收策略、集群和客户端等。
45 4
|
13天前
|
存储 算法 NoSQL
百度面试:如何用Redis实现限流?
百度面试:如何用Redis实现限流?
29 2
|
19小时前
|
存储 缓存 NoSQL
Redis八股文(大厂面试真题)
Redis八股文(大厂面试真题)
22 0
Redis八股文(大厂面试真题)
|
3天前
|
存储 NoSQL Redis
redis面试题库
redis面试题库
|
4天前
|
存储 NoSQL 算法
Redis(四):del/unlink 命令源码解析
Redis(四):del/unlink 命令源码解析
|
7天前
|
存储 缓存 NoSQL
Redis是一种高性能的内存数据库,常用于高并发环境下的缓存解决方案
【6月更文挑战第18天】**Redis摘要:** 高性能内存数据库,擅长高并发缓存。数据存内存,访问迅速;支持字符串、列表等多元数据类型;具备持久化防止数据丢失;丰富命令集便于操作;通过节点集群实现数据分片与负载均衡,增强可用性和扩展性。理想的缓存解决方案。
22 1
|
13天前
|
存储 缓存 NoSQL
【面试宝藏】Redis 常见面试题解析其二
Redis 高级面试题涵盖了哈希槽机制、集群的主从复制、数据丢失可能性、复制机制、最大节点数、数据库选择、连通性测试、事务操作、过期时间和内存优化等。Redis 使用哈希槽实现数据分布,主从复制保障高可用,异步复制可能导致写操作丢失。集群最大支持1000个节点,仅允许单数据库。可通过 `ping` 命令测试连接,使用 `EXPIRE` 设置过期时间,`MULTI/EXEC` 等进行事务处理。内存优化包括合理数据类型、设置过期时间及淘汰策略。Redis 可用作缓存、会话存储、排行榜等场景,使用 `SCAN` 查找特定前缀键,列表实现异步队列,分布式锁则通过 `SET` 命令和 Lua 脚本实现。
24 5
|
5天前
|
NoSQL 安全 Redis
redis持久化方式—RDB
redis持久化方式—RDB
21 0
|
5天前
|
缓存 NoSQL 安全
深入理解redis持久化—AOF日志
深入理解redis持久化—AOF日志
12 0
|
2天前
|
存储 关系型数据库 MySQL