聊聊 Redis 是如何进行请求处理
概述
首先是注册处理器;
开启循环监听端口,每监听到一个连接就会创建一个 Goroutine;
然后就是 Goroutine 里面会循环的等待接收请求数据,然后根据请求的地址去处理器路由表中匹配对应的处理器,然后将请求交给处理器处理;
用代码表示就是这样:
Copy
func (srv *Server) Serve(l net.Listener) error {
…
baseCtx := context.Background()
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
// 接收 listener 过来的网络连接
rw, err := l.Accept()
…
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew)
// 创建协程处理连接
go c.serve(connCtx)
}
}
对于 Redis 来说就有些不太一样,因为它是单线程的,无法使用多线程处理连接,所以 Redis 选择使用基于 Reactor 模式的事件驱动程序来实现事件的并发处理。
在 Redis 中所谓 Reactor 模式就是通过 epoll 来监听多个 fd,每当这些 fd 有响应的时候会以事件的形式通知 epoll 进行回调,每一个事件都有一个对应的事件处理器。
如: accept 对应 acceptTCPHandler 事件处理器、read & write 对应readQueryFromClient 事件处理器等,然后通过事件的循环派发的形式将事件分配给事件处理器进行处理。
所以说上面的这个 Reactor 模式都是通过 epoll 来实现的,对于 epoll 来说主要有这三个方法:
Copy
//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
int epoll_create(int size);
/*
- 可以理解为,增删改 fd 需要监听的事件
- epfd 是 epoll_create() 创建的句柄。
- op 表示 增删改
- epoll_event 表示需要监听的事件,Redis 只用到了可读,可写,错误,挂断 四个状态
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*
- 可以理解为查询符合条件的事件
- epfd 是 epoll_create() 创建的句柄。
- epoll_event 用来存放从内核得到事件的集合
- maxevents 获取的最大事件数
- timeout 等待超时时间
*/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
所以我们可以根据这三个方法实现一个简单的 server:
Copy
// 创建监听
int listenfd = ::socket();
// 绑定ip和端口
int r = ::bind();
// 创建 epoll 实例
int epollfd = epoll_create(xxx);
// 添加epoll要监听的事件类型
int r = epoll_ctl(…, listenfd, …);
struct epoll_event* alive_events = static_cast<epoll_event*>(calloc(kMaxEvents, sizeof(epoll_event)));
while (true) {
// 等待事件
int num = epoll_wait(epollfd, alive_events, kMaxEvents, kEpollWaitTime);
// 遍历事件,并进行事件处理
for (int i = 0; i < num; ++i) {
int fd = alive_events[i].data.fd;
// 获取事件
int events = alive_events[i].events;
// 进行事件的分发
if ( (events & EPOLLERR) || (events & EPOLLHUP) ) {
…
} else if (events & EPOLLRDHUP) {
…
}
…
}
}
调用流程#
所以根据上面的介绍,可以知道对于 Redis 来说一个事件循环无非也就这么几步:
注册事件监听及回调函数;
循环等待获取事件并处理;
调用回调函数,处理数据逻辑;
回写数据给 Client;
注册 fd 到 epoll 中,并设置回调函数 acceptTcpHandler,如果有新连接那么会调用回调函数;
启动一个死循环调用 epoll_wait 等待并持续处理事件,待会我们回到 aeMain 函数中循环调 aeProcessEvents 函数;
当有网络事件过来的时候,会顺着回调函数 acceptTcpHandler 一路调用到 readQueryFromClient 进行数据的处理,readQueryFromClient 会解析 client 的数据,找到对应的 cmd 函数执行;
Redis 实例在收到客户端请求后,会在处理客户端命令后,将要返回的数据写入客户端输出缓冲区中而不是立马返回;
然后在 aeMain 函数每次循环时都会调用 beforeSleep 函数将缓冲区中的数据写回客户端;
上面的整个事件循环的过程实际上代码步骤已经写的非常清晰,网上也有很多文章介绍,我就不多讲了。
命令执行过程 & 回写客户端#
命令执行#
下面我们讲点网上很多文章都没提及的,看看 Redis 是如何执行命令,然后存入缓存,以及将数据从缓存写回 Client 这个过程。
在前一节我们也提到了,如果有网络事件过来的时候会调用到 readQueryFromClient 函数,它是真正执行命令的地方。我们也就顺着这个方法一直往下看:
readQueryFromClient 里面会调用 processInputBufferAndReplicate 函数处理请求的命令;
在 processInputBufferAndReplicate 函数里面会调用 processInputBuffer 以及判断一下如果是集群模式的话,是否需要将命令复制给其他节点;
processInputBuffer 函数里面会循环处理请求的命令,并根据请求的协议调用 processInlineBuffer 函数,将 redisObject 对象后调用 processCommand 执行命令;
processCommand 在执行命令的时候会通过 lookupCommand 去 server.commands 表中根据命令查找对应的执行函数,然后经过一系列的校验之后,调用相应的函数执行命令,调用 addReply 将要返回的数据写入客户端输出缓冲区;
server.commands会在 populateCommandTable 函数中将所有的 Redis 命令注册进去,作为一个根据命令名获取命令函数的表。
比如说,要执行 get 命令,那么会调用到 getCommand 函数:
Copy
void getCommand(client *c) {
getGenericCommand©;
}
int getGenericCommand(client *c) {
robj *o;
// 查找数据
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
return C_OK;
…
}
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
//到db中查找数据
robj *o = lookupKeyRead(c->db, key);
// 写入到缓存中
if (!o) addReply(c,reply);
return o;
}
在 getCommand 函数中查找到数据,然后调用 addReply 将要返回的数据写入客户端输出缓冲区。
数据回写客户端
在上面执行完命令写入到缓冲区后,还需要从缓冲区取出数据返回给 Client。对于数据回写客户端这个流程来说,其实也是在服务端的事件循环中完成的。
首先 Redis 会在 main 函数中调用 aeSetBeforeSleepProc 函数将回写包的函数 beforeSleep 注册到 eventLoop 中去;
然后 Redis 在调用 aeMain 函数进行事件循环的时候都会判断一下 beforesleep 有没有被设值,如果有,那么就会进行调用;
beforesleep 函数里面会调用到 handleClientsWithPendingWrites 函数,它会调用 writeToClient 将数据从缓冲区中回写给客户端;
聊聊 Redis 是如何进行请求处理相关推荐
- 聊聊redis分布式锁的8大坑
在分布式系统中,由于redis分布式锁相对于更简单和高效,成为了分布式锁的首先,被我们用到了很多实际业务场景当中. 但不是说用了redis分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引来一些 ...
- 聊聊 Redis 使用场景 1
原文地址:Redis实战(五) 聊聊Redis使用场景 博客地址:http://blog.720ui.com/ 使用场景说明 计数器 数据统计的需求非常普遍,通过原子递增保持计数.例如,点赞数.收藏数 ...
- 聊聊redis分布式锁的8大坑!
聊聊redis分布式锁的8大坑!不是说用了redis分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引来一些意想不到的问题.https://mp.weixin.qq.com/s/8NFqfsQ8 ...
- 聊聊Redis的数据热点问题
前两天,我们使用的某云厂商服务挂了,而且一挂就是挂大半天,我们的服务强依赖于他们,所以我们也跟着一起挂.然而我们却无能为力,只能等他们恢复.事故原因中听他们提到Redis有个热key,正好我在上家 ...
- 聊聊redis的HealthIndicator
序 本文主要研究一下redis的HealthIndicator RedisHealthIndicator spring-boot-actuator-2.0.4.RELEASE-sources.jar! ...
- 聊聊 Redis 使用场景
随着数据量的增长,MySQL 已经满足不了大型互联网类应用的需求.因此,Redis 基于内存存储数据,可以极大的提高查询性能,对产品在架构上很好的补充.在某些场景下,可以充分的利用 Redis 的特性 ...
- lua运行外部程序_一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩
本节内容 展示Redis数据库在高并发下实现抢口罩的例子,文章分3个实现的模式 不加锁 乐观锁 Lua脚本 前言 写于2020.2.8日疫情严重之日,抢口罩成为每晚8点档黄金时间必备节目,在报道上看到 ...
- @cacheable 设置过期时间_缓存面试三连击——聊聊Redis过期策略?内存淘汰机制?再手写一个LRU 吧!...
大家好,今天我和大家想聊一聊有关redis的过期策略的话题. 听到这里你也许会觉得:"我去,我只是个日常搬砖的,这种偏底层的知识点,我需要care吗?" 话虽如此·,但是兄die, ...
- 今天来聊聊 Redis 的主从复制
作者 | 阿Q 来源 | 阿Q说代码 今天我们就从配置文件.设计原理.面试真题三个方面来聊一聊 Redis 的主从复制. 在 Redis 复制的基础上,使用和配置主从复制非常简单,能使得从 Redis ...
- 服务端指南 数据存储篇 | 聊聊 Redis 使用场景(转)
作者:梁桂钊 本文,是升级版,补充部分实战案例.梳理几个场景下利用 Redis 的特性可以大大提高效率. 随着数据量的增长,MySQL 已经满足不了大型互联网类应用的需求.因此,Redis 基于内存存 ...
最新文章
- springboot 集成jpa_基于Spring Boot+JPA Restful 风格的数据
- 《Generative Face Completion》论文笔记
- 算法学习之路|数位dp简要分析
- 自动ping博客服务程序
- dsp调音一次多少钱_DSP调音中EQ使用技巧
- SAS︱数据索引、数据集常用操作(set、where、merge、append)
- Ubuntu中安装网易云音乐(可以直接打开的最简单的方法)
- android计算器编程思路,android计算器---思路以及计算器功能梳理(未完成)(示例代码)...
- google 常用的技术搜索关键词
- Android 自动检测版本更新(包含强制更新)并安装
- PMP项目管理是什么意思?
- python读书笔记—读写文件
- 【强化学习】手把手教你实现游戏通关AI(1)——游戏界面实现
- 运营技巧︱用户运营中,如何提高用户转化率
- 数据库(MYSQL)之元数据
- mysql历史数据自动归档
- 虚拟机安装windows10
- Java中如何创建一个文件或者文件夹
- maya 表达式编辑器无法正常打开
- 串口通信之————IIC(软件驱动)
热门文章
- 谷歌学术首页url爬取
- 笔记本w ndows未能启动,手把手教你windows无法启动怎么办
- 去掉右键的“使用skype共享”
- 又一个程序员突然倒地,身体这件事一定要警钟长鸣!
- js实现复制input隐藏域的取巧做法
- 干货!推荐系统中的异构关系学习
- LeetCode——5731. 座位预约管理系统(Seat Reservation Manager)[中等]——分析及代码(Java)
- ROVIO mobile webcam 路威机器人
- Excel 2010 VBA 入门 128 在窗体中插入控件
- 蓝桥本第九届省赛刷题记录