Redis源码剖析——客户端和服务器

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接。这篇文章将通过源码看看客户端和服务器的底层数据结构和工作过程在Redis这种一对多的服务模式下,每个客户端可以向服务器发送命令请求,而服务器则接收并处理客户端发送的命令请求,并向客户端返回命令回复。

Redis服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接。这篇文章将通过源码看看客户端和服务器的底层数据结构和工作过程

在Redis这种一对多的服务模式下,每个客户端可以向服务器发送命令请求,而服务器则接收并处理客户端发送的命令请求,并向客户端返回命令回复。通过使用由I/O多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式来处理命令请求,并与多个客户端进行网络通信。

客户端
客户端数据结构
客户端底层的数据结构如下:

typedef struct redisClient {
uint64_t id; / Client incremental unique ID. /
// 套接字描述符
int fd;
redisDb db;
int dictid;
// 客户端名字
robj
name; / As set by CLIENT SETNAME /
// 输入缓冲区,保存客户端发送的命令请求
sds querybuf;
size_t querybuf_peak; / Recent (100ms or more) peak of querybuf size /
// 命令和命令参数
int argc;
robj *argv;
// 命令实现函数字典
struct redisCommand
cmd, lastcmd;
int reqtype;
int multibulklen; /
number of multi bulk arguments left to read /
long bulklen; /
length of bulk argument in multi bulk request /
list
reply;
unsigned long reply_bytes; / Tot bytes of objects in reply list /
int sentlen; / Amount of bytes already sent in the current
buffer or object being sent.
/
// 创建客户端时间
time_t ctime; / Client creation time /
// 客户端和服务器最后一次进行互动的时间
time_t lastinteraction; / time of the last interaction, used for timeout /
time_t obuf_soft_limit_reached_time;
// 标志,记录客户端的角色
int flags; / REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... /
// 标志是否通过身份验证
int authenticated; / when requirepass is non-NULL /
... // 其他相关属性

/* Response buffer */
// 回应缓冲区
int bufpos;
char buf[REDIS_REPLY_CHUNK_BYTES];

} redisClient;
在客户端的各个属性中:

fd表示套接字描述符,伪客户端的fd属性的值为-1:伪客户端处理的命令请求来源于AOF文件或者Lua脚本,而不是网络,所以这种客户端不需要套接字连接;普通客户端的fd属性的值为大于-1的整数

命令和命令参数是对输入缓冲的命令进行解析以后获得命令和参数。

cmd 是命令的实现函数的数组,命令实现函数的结构如下:

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. */

...

// 指定哪些参数是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 */
// 统计信息
long long microseconds, calls;

};
客户端的创建和关闭
当客户端向服务器发出connect请求的时候,服务器的事件处理器就会对这个事件进行处理,创建相应的客户端状态,并将这个新的客户端状态添加到服务器状态结构clients链表的末尾

/*

  • 创建一个新客户端
    /
    redisClient
    createClient(int fd){

    // 分配空间
    redisClient *c = zmalloc(sizeof(redisClient));

    // 当 fd 不为 -1 时,创建带网络连接的客户端
    // 如果 fd 为 -1 ,那么创建无网络连接的伪客户端
    // 因为 Redis 的命令必须在客户端的上下文中使用,所以在执行 Lua 环境中的命令时
    // 需要用到这种伪终端
    if (fd != -1) {
    // 非阻塞
    anetNonBlock(NULL,fd);
    // 禁用 Nagle 算法
    anetEnableTcpNoDelay(NULL,fd);
    // 设置 keep alive
    if (server.tcpkeepalive)
    anetKeepAlive(NULL,fd,server.tcpkeepalive);
    // 绑定读事件到事件 loop (开始接收命令请求)
    if (aeCreateFileEvent(server.el,fd,AE_READABLE,
    readQueryFromClient, c) == AE_ERR)
    {
    close(fd);
    zfree(c);
    return NULL;
    }
    }

    // 初始化各个属性

    // 默认数据库
    selectDb(c,0);
    // 套接字
    c->fd = fd;
    ...

    listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
    listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
    // 如果不是伪客户端,那么添加到服务器的客户端链表中
    if (fd != -1) listAddNodeTail(server.clients,c);
    // 初始化客户端的事务状态
    initClientMultiState(c);

    // 返回客户端
    return c;
    }
    对于客户端的启动程序,其大致的逻辑是:读取本地配置,连接服务器获取服务器的配置,获取本地输入的命令并发送到服务器

一个普通客户端可以因为多种原因而被关闭:

如果客户端进程退出或者被杀死,那么客户端与服务器之间的网络连接将被关闭,从而造成客户端被关闭。
如果客户端向服务器发送了带有不符合协议格式的命令请求,那么这个客户端也会被服务器关闭。
如果客户端成为了CLIENT KLLL命令的目标,那么它也会被关闭。
关闭客户端的底层实现:

/*

  • 释放客户端
    /
    void freeClient(redisClient
    c){
    listNode *ln;

    ...

    / Free the query buffer /
    sdsfree(c->querybuf);
    c->querybuf = NULL;

    / Deallocate structures used to block on blocking ops. /
    if (c->flags & REDIS_BLOCKED) unblockClient(c);
    dictRelease(c->bpop.keys);

    / UNWATCH all the keys /
    // 清空 WATCH 信息
    unwatchAllKeys(c);
    listRelease(c->watched_keys);

    / Unsubscribe from all the pubsub channels /
    // 退订所有频道和模式
    pubsubUnsubscribeAllChannels(c,0);
    pubsubUnsubscribeAllPatterns(c,0);
    dictRelease(c->pubsub_channels);
    listRelease(c->pubsub_patterns);

    /* Close socket, unregister events, and remove list of replies and

    • accumulated arguments. */
      // 关闭套接字,并从事件处理器中删除该套接字的事件
      if (c->fd != -1) {
      aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
      aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
      close(c->fd);
      }

    // 清空回复缓冲区
    listRelease(c->reply);

    // 清空命令参数
    freeClientArgv(c);

    / Remove from the list of clients /
    // 从服务器的客户端链表中删除自身
    if (c->fd != -1) {
    ln = listSearchKey(server.clients,c);
    redisAssert(ln != NULL);
    listDelNode(server.clients,ln);
    }

    // 删除客户端的阻塞信息
    if (c->flags & REDIS_UNBLOCKED) {
    ln = listSearchKey(server.unblocked_clients,c);
    redisAssert(ln != NULL);
    listDelNode(server.unblocked_clients,ln);
    }

    ...

    if (c->name) decrRefCount(c->name);
    // 清除参数空间
    zfree(c->argv);
    // 清除事务状态信息
    freeClientMultiState(c);
    sdsfree(c->peerid);
    // 释放客户端 redisClient 结构本身
    zfree(c);
    }``

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
4天前
|
缓存 监控 Java
Java Socket编程最佳实践:优化客户端-服务器通信性能
【6月更文挑战第21天】Java Socket编程优化涉及识别性能瓶颈,如网络延迟和CPU计算。使用非阻塞I/O(NIO)和多路复用技术提升并发处理能力,减少线程上下文切换。缓存利用可减少I/O操作,异步I/O(AIO)进一步提高效率。持续监控系统性能是关键。通过实践这些策略,开发者能构建高效稳定的通信系统。
|
4天前
|
Java 应用服务中间件 开发者
【实战指南】Java Socket编程:构建高效的客户端-服务器通信
【6月更文挑战第21天】Java Socket编程用于构建客户端-服务器通信。`Socket`和`ServerSocket`类分别处理两端的连接。实战案例展示了一个简单的聊天应用,服务器监听端口,接收客户端连接,并使用多线程处理每个客户端消息。客户端连接服务器,发送并接收消息。了解这些基础,加上错误处理和优化,能帮你开始构建高效网络应用。
|
4天前
|
IDE Java 开发工具
从零开始学Java Socket编程:客户端与服务器通信实战
【6月更文挑战第21天】Java Socket编程教程带你从零开始构建简单的客户端-服务器通信。安装JDK后,在命令行分别运行`SimpleServer`和`SimpleClient`。服务器监听端口,接收并回显客户端消息;客户端连接服务器,发送“Hello, Server!”并显示服务器响应。这是网络通信基础,为更复杂的网络应用打下基础。开始你的Socket编程之旅吧!
|
3天前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
|
4天前
|
安全 Java 网络安全
Java Socket编程教程:构建安全可靠的客户端-服务器通信
【6月更文挑战第21天】构建安全的Java Socket通信涉及SSL/TLS加密、异常处理和重连策略。示例中,`SecureServer`使用SSLServerSocketFactory创建加密连接,而`ReliableClient`展示异常捕获与自动重连。理解安全意识,如防数据截获和中间人攻击,是首要步骤。通过良好的编程实践,确保网络应用在复杂环境中稳定且安全。
|
4天前
|
Java 数据安全/隐私保护
深入剖析:Java Socket编程原理及客户端-服务器通信机制
【6月更文挑战第21天】Java Socket编程用于构建网络通信,如在线聊天室。服务器通过`ServerSocket`监听,接收客户端`Socket`连接请求。客户端使用`Socket`连接服务器,双方通过`PrintWriter`和`BufferedReader`交换数据。案例展示了服务器如何处理每个新连接并广播消息,以及客户端如何发送和接收消息。此基础为理解更复杂的网络应用奠定了基础。
|
2天前
|
Java Android开发
Java Socket编程示例:服务器开启在8080端口监听,接收客户端连接并打印消息。
【6月更文挑战第23天】 Java Socket编程示例:服务器开启在8080端口监听,接收客户端连接并打印消息。客户端连接服务器,发送"Hello, Server!"后关闭。注意Android中需避免主线程进行网络操作。
12 4
|
17小时前
|
NoSQL Redis 数据安全/隐私保护
连接测试服务器redis
连接测试服务器redis
8 1
|
4天前
|
存储 NoSQL 算法
Redis(四):del/unlink 命令源码解析
Redis(四):del/unlink 命令源码解析
|
3天前
|
网络协议 Java Linux
探索Java Socket编程:实现跨平台客户端-服务器通信的奥秘
【6月更文挑战第21天】Java Socket编程示例展示了如何构建跨平台聊天应用。服务器端使用`ServerSocket`监听客户端连接,每个连接启动新线程处理。客户端连接服务器,发送并接收消息。Java的跨平台能力确保代码在不同操作系统上无需修改即可运行,简化开发与维护。

热门文章

最新文章