源码角度分析Redis的请求处理逻辑

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Redis客户端在发送诸如get、set等命令时,服务端响应并发送回复,整个链路采用了request-reply网络处理模型。本文从源码角度主要分析服务端如何处理来自客户端的request:即服务端采取的事件处理机制、如何响应客户端的连接建立及读写请求。从C++, java和C语言版本的客户端源码展开阐述客户端如何接收和解析来自服务端的reply。服务端事件处理总体架构Redis服务端与客户端的⽹

Redis客户端在发送诸如get、set等命令时,服务端响应并发送回复,整个链路采用了request-reply网络处理模型。本文从源码角度主要分析服务端如何处理来自客户端的request:即服务端采取的事件处理机制、如何响应客户端的连接建立及读写请求。从C++, java和C语言版本的客户端源码展开阐述客户端如何接收和解析来自服务端的reply。

服务端事件处理总体架构

Redis服务端与客户端的⽹络IO通过事件驱动完成:连接建⽴、连接套接字读写封装成事件;由事件分发器管理这些事

件的添加、删除、更新和分发。常⻅的⾼性能事件分发器有 epoll, kqueue 等。服务端启动后,为⽹络IO事件的存储分

配空间、执⾏事件循环。每⼀轮循环中,往事件管理器添加事件或更新事件的状态;若有事件触发,则依次将被触发

的事件转移到事件发布队列内,并遍历该队列依次处理事件,执⾏⽹络 IO,事件驱动模型图如下图所示。

代码脉络

服务端⽹络IO事件处理机制

redis内部⾃⾏实现类似libuv中的event loop机制:⽂件ae.h和ae.c。⽀持多种IO多路复⽤器(epoll, kqueue,…);其

中,常⻅的⽹络IO事件以aeFileEvent类抽象,mask表示当前事件的类型、rfileProc和wfileProc是当读或写事件发⽣时,根

据事件类型执⾏相应的回调函数。

Redis服务端启动后,向事件管理器注册监听套接字事件。当有来⾃客户端的连接建立请求时,监听套接字事件对应的事件

处理函数acceptTcpHandler会建⽴连接套接字并调⽤createClient函数初始化客户端数据结构;并且为该客户端创建

可读事件,该事件的对应处理函数是conn->type->ae_handler。

在后续服务端同客户端的⽹络IO中,当某套接字上有读事件或写事件发⽣时,eventLoop中会去执⾏ae_handler也就

是下图中的connSocketEventHandler函数;该函数内部会进⼀步执⾏该套接字对应事件在加⼊IO多选器时注册的

read_handler和write_handler(该handler通过set_read/write_handler函数注册);⽐如执⾏read_handler:

readQueryFromClient、write_handler: replyToClient。readQueryFromClient主要读取服务端内核缓冲区的请求指令,并进行相应的哈希计算与查找;当内核缓冲区有空间可写入reply时,服务端会调用replyToClient函数将计算的结果回复到客户端。

客户端事件处理运⽤机制

客户端可采用同步通信的方式:发送命令,并等待服务端执行结果后的回复;亦可采用异步的通信方式,即客户端发送命令后,可执行其他操作,当服务端回复到客户端时,客户端响应获取该回复即可。

客户端hiredis的异步IO实现中,仍然使⽤事件驱动机制实现(此处仍然以redis内部基于ae.h的实现为例):通过

redisAeAddRead和redisAeAddWrite函数分别向IO多路分发器内发射客户端可读/可写事件;可以观察到当有读事件发

⽣时会调⽤redisAeReadEvent函数(写事件发⽣时同理);redisAeReadEvent函数内部调⽤的

redisAsyncHandleRead进⽽会调⽤hiredis.h中的bufferRead和getReply函数(调⽤同步IO⽅式下的hiredis.c内部实

现),也会调⽤其他回调函数。

使⽤hiredis库实现异步的Redis客户端时,可通过函数调⽤redisAsyncCommand,触发写事件后,将缓冲区的数据异

步发送⾄服务端;服务端响应命令后,触发客户端eventloop中事件处理器的读事件:客户端执⾏redisAsyncRead,

⼀⽅⾯读取输⼊缓冲区,另⼀⽅⾯重新往事件处理器中注册读事件,并执⾏应⽤层传⼊的回调函数

(redisProcessCallbacks(redisAsyncContext))。回调函数的函数原型是void (*callback)(const redisAsyncContext,

void *r, void *privatedata)。

Redis客户端通信实现

本节主要阐述不同语言版本下,异步Redis客户端的大致实现思路。此外,上文提到过,服务端通过replyToClient将reply通过网络回复到客户端。客户端接收的数据满足RESP3协议,需要将该网络数据反序列化成Redis对象(字符串、数组等),C++版本的redis-plus-plus客户端底层复用了hiredis的实现,Java Netty版本自行实现了该反序列化过程。因此,重点讲述Netty redis和hiredis数据反序列化的实现。

redis-plus-plus客户端

redis-plus-plus是c++实现的redis客户端库

在redis-plus-plus实现中,使⽤libuv实现事件驱动机制,进⽽实现客户端异步IO,利⽤hiredis库中的

redisLibuvAttatch建⽴redis-plus-plus同hiredis的关联。

redis-plus-plus中,可通过构造AsyncRedis启动异步的Redis客户端:在AsyncRedis的构造函数中,会创建uv_loop_t

对象,并创建⼀个⼦线程执⾏event_loop的逻辑。在AsyncRedis的析构函数中,由于主线程(调⽤析构函数的线程)

和eventloop线程共享某些变量,所以需要额外的同步机制保证线程安全。

在执⾏异步IO过程时,事件多路选择器调⽤IO事件对应的回调函数时是在区别于主线程的⼦线程中执⾏的(该⼦线

程也是libuv创建的eventLoop绑定的线程)。如下图,异步的set和get命令的处理及应⽤层传⼊的回调函数均会在⼦

线程中执⾏【set key1 val1(返回1) get key1(返回val1)】。

Netty Redis客户端

基于Netty的Redis客户端实现了异步的⽹络IO模型。

客户端与服务端连接建立以后,为客户端构造channelPipeLine,往该pipeline中加⼊ChannelHandler(实现InboundHandler);并以此构造Channel;Channel构造完成后,绑定到某⼀个Netty EventLoop中。

Netty-redis客户端接收到服务端发送的回复后,经由ByteToMessageDecoder将⽹络中的字节数据转成RedisMessage

类(转换的过程遵循RESP3协议);具体来讲,RiedsDecoder实现了ByteToMessageDecoder接⼝,内部采⽤状态机

⽅式将⽹络中的字节流转换成RedisMessage的各个⼦类的功能。以string类型和array类型的回复的字节流为例,

RedisDecoder会将字节流相应的转换成ArrayHeaderRedisMessage类和BulkStringHeaderRedisMessage类。

上述的Header类表明其后会尾随多个RedisMessage⼦类;多个RedisMessage可经由BulkStringAggregator类或

ArrayAggregator类聚合成⼀个RedisMessage⼦类。前者聚合成RedisMessage,后者聚合成⼀个

ArrayRedisMessage。

RedisBulkStringAggregator继承⾃MessageAggregator;后者和RedisArrayAggregator都继承⾃

MessageToMessageDecoder类(重载该类中的decode⽅法)。当服务端的reply数据包含嵌套层级结构时,RedisArrayAggregator类可通过深度优先搜索拆解该结构。其内部的成员depths⽤于存储数组类型嵌套情形下,递归解析时的上下⽂(AggregateState),depths当前的size表示⽬前数组嵌套的层数。

Hiredis客户端

hiredis中,也涉及从服务端获取的⽹络数据按照RESP3协议解析的过程(read.c⽂件中实现)。其中,处理数

组/Map/Set等聚合型数据的算法类似于Netty中的实现。

下图中,redisReader结构体内与redisReadTask遍历相关的属性定义包括tasks(相当于解析栈的总深度)。

buf属性存储了客户端从服务端读取的回复,len表示回复的⻓度;pos记录了当前处理数据的位置;ridx表明当前当前

所处的解析栈的深度,解析嵌套层次时,深度优先解析完当前任务后,再返回到上⼀层任务;当存在嵌套解析的情况

时,ridx递增。

下图中read.h⽂件定义的redisReadTask结构体相当于netty-redis中定义的AggregateState。每⼀层解析当前数组

时,数组的⻓度为elements,解析的当前数组的遍历下标为idx。

redisReaderTask结构中,idx表示当前构造的redis对象(string, integer, array ...)在数组中的位置,数组的元素(redis

对象)内容由obj赋予;当且仅当idx等于其⽗节点(相当于递归调⽤栈的上⼀层)的数组⼤⼩(elements属性)时,当前

数组解析结束;调⽤栈pop,返回上⼀层继续解析。这段逻辑在read.c⽂件中的moveToNextTask函数中。

总结

通过源码分析Redis服务端和异步客户端实现方式,可以看出基于事件驱动的网络请求处理模型的应用;理解Redis命令处理背后的原理。此外,源码中的一些实现方式,比如Netty中为反序列化RESP3协议数据所设计的类结构、深度优先解析嵌套层级结构数据的实现方法可作为参考用于日常或工作中的类似场景中。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
相关文章
|
5月前
|
存储 NoSQL 数据库
Redis 逻辑数据库与集群模式详解
Redis 是高性能内存键值数据库,广泛用于缓存与实时数据处理。本文深入解析 Redis 逻辑数据库与集群模式:逻辑数据库提供16个独立存储空间,适合小规模隔离;集群模式通过分布式架构支持高并发和大数据量,但仅支持 database 0。文章对比两者特性,讲解配置与实践注意事项,并探讨持久化及性能优化策略,助你根据需求选择最佳方案。
186 5
|
3月前
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
225 0
|
11月前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
233 1
|
8月前
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
249 36
|
10月前
|
缓存 监控 NoSQL
Redis 缓存穿透的检测方法与分析
【10月更文挑战第23天】通过以上对 Redis 缓存穿透检测方法的深入探讨,我们对如何及时发现和处理这一问题有了更全面的认识。在实际应用中,我们需要综合运用多种检测手段,并结合业务场景和实际情况进行分析,以确保能够准确、及时地检测到缓存穿透现象,并采取有效的措施加以解决。同时,要不断优化和改进检测方法,提高检测的准确性和效率,为系统的稳定运行提供有力保障。
196 5
|
11月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
108 2
|
11月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
161 3
|
12月前
|
Oracle NoSQL 关系型数据库
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
2095 3
|
12月前
|
存储 Prometheus NoSQL
Redis 内存突增时,如何定量分析其内存使用情况
【9月更文挑战第21天】当Redis内存突增时,可采用多种方法分析内存使用情况:1)使用`INFO memory`命令查看详细内存信息;2)借助`redis-cli --bigkeys`和RMA工具定位大键;3)利用Prometheus和Grafana监控内存变化;4)优化数据类型和存储结构;5)检查并调整内存碎片率。通过这些方法,可有效定位并解决内存问题,保障Redis稳定运行。
795 4
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。