Redis网络模型

目录

Redis为什么快?

Redis到底是单线程还是多线程?

Redis网络模型

初始化

事件循环


Redis为什么快?

Redis之所以能够提供高速的性能主要得益于两个方面:

  • 内存存储:除了持久化外,Redis的操作都在内存中,这是其性能优势的根本。内存的读写速度远远快于磁盘,因此Redis能够实现极低的读写延迟。
  • **单线程与IO多路复用模型:**Redis采用了单线程的事件驱动模型,并结合了IO多路复用技术。单线程处理避免了多线程的锁机制问题,降低了上下文切换的开销。IO多路复用允许一个线程同时监控多个文件描述符(即多个连接),一旦某个连接的状态发生变化,该线程就会立即处理该事件,从而高效地处理大量客户端连接。

本文我们会为大家介绍 Redis 的网络模型。

Redis到底是单线程还是多线程?

有一个常见的面试问题:Redis到底是单线程还是多线程?

如果仅仅聊Redis的核心业务部分(命令处理),答案是单线程,如果是聊整个Redis,那么答案就是多线程。

Redis在核心业务的处理即命令的执行是由单线程执行 ,Redis 6.0开始,Redis引入了多线程IO模型。网络数据的读写和请求协议解析可以通过多个IO线程来处理,但命令的执行仍然由主线程串行执行在Redis4.0版本中引入了多线程来异步处理一些耗时任务 ,如对于大key的异步删除命令 unlink 和异步持久化 bgsave命令 等。避免这些操作阻塞主线程,从而提高性能。

因此,对于Redis的核心网络模型,在Redis 6.0之前确实都是单线程。是利用epoll(Linux系统)这样的IO多路复用技术在事件循环中不断处理客户端情况。

为什么Redis要选择单线程?

  • 抛开持久化不谈,Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,因此多线程并不会带来巨大的性能提升。多线程会导致过多的上下文切换,带来不必要的开销。引入多线程会面临线程安全问题,必然要引入线程锁这样的安全手段,实现复杂度增高,而且性能也会大打折扣

Redis网络模型

Redis通过IO多路复用来提高网络性能,并且支持各种不同的多路复用实现,并且将这些实现进行封装, 提供了统一的高性能事件库API库 AE。

AE事件库是Redis用来处理IO事件的库,它根据系统的不同选用尽可能高效率的IO实现。在ae.c中使用条件编译根据系统支持的多路复用器来选用相应的文件,其按优先级排序为:evport、epoll、kqueue、select。在各个源文件中实现了ae库基本操作的统一接口。

Redis单线程网络模型的整个流程如下:

初始化

这是简化后的Redis源码中的主函数,我们先看initServer() 函数,

在初始化中会先调用 aeCreateEventLoop() 这个函数会创建IO多路复用实例,类似于 epoll_create() 。

Redis 使用 TCP 协议进行网络通信,因此我们需要创建一个监听套接字,用来接收客户端的连接请求,这里调用了 listenToPort() 函数。

createSockerAcceptHandler() 函数是绑定一个回调函数,当epoll 检测到监听套接字有读事件时,epoll 会调用该函数,即事件分发。

在数据读处理器中会接受客户端的连接,然后将客户端的套接字加入 epoll 关心读事件,同时绑定读处理器,即客户端发生读事件时会调用客户端读处理函数 readQueryFromClient()。

最后 initSrever() 会通过 aeSetBeforeSleepProc() 设置一个回调函数 aeSetBeforeSleepProc(),该函数将在Redis的事件循环每次进入睡眠(即等待事件)之前被调用。

事件循环

aeMain函数会进行监听事件循环。

eventLoop->stop 是初始化事件循环的停止标志。这里将其设置为0,表示事件循环不停止。

在循环中,调用 aeProcessEvents 函数来处理事件。

进入等待事件之前,调用 beforeSleep() 回调函数。这个回调函数在事件循环的每个迭代开始时执行用于执行定时任务、清理工作或其他需要定期执行的操作比如清理 TTL 过期的key。

然后调用 aeApiPoll 函数,负责等待文件描述符就绪。对应于 epoll_wait,tvp 是一个指向时间结构的指针,用于指定等待事件的最长时间。numevents 将存储就绪事件的数量。

在等待结束后,遍历所有就绪的事件。对于每个就绪的事件,执行相应的事件处理函数。

对于监听套接字,是连接应答处理器,对应客户端套接字,是命令请求处理器和命令回复处理器。

对于客户端的请求会执行如下逻辑,

这三个函数的功能简而言之就是读取客户端请求、解析命令、执行命令,然后保存到缓存区,如果用户的写缓存区满了,就将响应,写添加到待写出链表。然后将将客户端添加到服务器的待写队列,这个步骤并没有发送响应?那么响应什么时候发送呢?

在 beforesleep() 中即 epoll_wait 等待之前,会将待写出的客户端套接字加入监听写事件,同时绑定写处理器,在客户端套接字写事件触发后,会执行写处理,发回响应。

总体模型如下:

在整个处理过程中性能瓶颈主要在 IO 而非命令执行,因此 Redis 引入多线程,主要在命令解析,和响应回复。其核心命令执行还是单线程。

到此Redis网络模型介绍,本文就到这里。

参考:

黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目_哔哩哔哩_bilibili

相关推荐
DC_BLOG3 分钟前
数据结构树
java·数据结构·算法
老牛源码4 分钟前
240004基于Jamva+ssm+maven+mysql的房屋租赁系统的设计与实现
java·mysql·maven·ssm
AI人H哥会Java26 分钟前
【JAVA】Java项目实战—分布式微服务项目:分布式消息队列
java·开发语言
南宫生27 分钟前
力扣-图论-14【算法学习day.64】
java·学习·算法·leetcode·图论
alden_ygq1 小时前
etcd详解
linux·redis·etcd
唐骁虎2 小时前
Spring Boot 项目的默认推荐目录结构详解
java·springboot
liuyunshengsir2 小时前
crictl和ctr与docker的命令的对比
java·docker·eureka
Ttang232 小时前
Tomcat原理(5)——tomcat最终实现
java·开发语言·servlet·tomcat·intellij-idea
_BugMan2 小时前
【业务场景】订单超时取消的各种方案
redis·java-ee·rocketmq
涛粒子2 小时前
Redis 事务
数据库·redis·缓存