Redis执行原理
Redis高性能、开源的、基于KEY-VALUE的存储服务系统,通过提供多种键值数据类型来适应不同场景下的缓存与存储需求。
优点:执行非常快、支持丰富的数据结构、操作具有原子性、适用于多种场景使用
存储结构:String、list、map、set、sorted-set
Redis执行原理
Redis大致执行过程:

命令输入->客户端转换为redis协议(RESP协议)->socket发送->服务端处理->socket返回->执行结果返回给用户
Redis事件处理机制:
- 基于ae事件驱动模型,进行高效网络IO读写、命令执行、以及时间事件处理
- 网络IO读写处理采用了IO多路复用技术,通过对evport、epoll、kqueue、select进行封装,同时监听多个socket,并根据 socket 目前执行的任务,来为 socket 关联不同的事件处理器。
- 监听端口对应的socket收到连接请求后,会创建一个client 结构,通过该结构来对连接状态进行管理。在请求进入时,将请求命令读取缓冲并进行解析,并存入到 client 的参数列表。根据请求命令找到对应的redisCommand,最后根据命令协议,对请求参数进一步的解析、校验并执行。
- 时间事件通过执行serverCron,做一些统计更新、过期key处理、AOF及RDB持久化等辅助操作
文件事件处理机制:Reactor模式进行处理,分为连接socket、IO多路复用程序、文件事件分派器、事件处理器四部分。
原理:
IO多路复用同时监控多个socket,当socket产生文件事件时,IO多路复用会将所有产生事件的socket放入一个队列,通过队列有序的把文件事件通知给文件分配器
文件事件:
- AE_READABLE事件,socket变得可读或有新的socket可以应答时产生
- AE_WRITABLE事件,socket变得可写时产生,同时产生时AE_READABLE被优先处理
文件事件处理器:
- 客户端连接redis,会为socket关联连接应答处理器
- 客户端写,会为socket关联命令请求处理器
- 客户端读,会为socket关联命令回复处理器
处理过程:
- redis启动时,将连接应答处理器与AE_READABLE事件关联。客户端发起一个连接,会产生一个AE_READABLE事件,由该处理器来处理跟客户端建立连接,创建客户端对应的socket,并将这个socket的AE_READABLE事件跟命令请求处理器关联。
- 客户端发起请求:
- 在socket产生一个AE_READABLE事件,由响应的命 Redis 的核心处理线程是单进程单线程模型,所有命令的接受与处理、数据淘汰等都在主线程中进行,这些任务处理速度非常快。如果核心单线程还要处理那些慢任务,在处理期间,势必会阻塞用户的正常请求,导致服务卡顿。
Redis 引入了BIO后台线程,专门处理那些慢任务,从而保证和提升主线程的处理能力。采用了生产者-消费者模型。主线程是生产者,生产慢任务存放到任务队列,消费者是BIO线程,从队列获取任务并处理。
BIO处理:Redis启动时创建三个任务队列,并对应构建3个BIO线程。
- close关闭文件任务。rewriteaof完成后,主线程需要关闭旧的 AOF 文件,就向close队列插入一个旧 AOF文件的关闭任务。由 close 线程来处理。
- fysnc任务。Redis 将 AOF 数据缓冲写入文件内核缓冲后,需要定期将系统内核缓冲数据写入磁盘,此时可以向 fsync 队列写入一个同步文件缓冲的任务,由 fsync 线程来处理。
- lazyfree任务。Redis 在需要淘汰元素数大于 64 的聚合类数据类型时,如列表、集合、哈希等,就往延迟清理队列中写入待回收的对象,由 lazyfree 线程后续进行异步回收。
当主线程有慢任务需要异步处理时。就会向对应的任务队列提交任务。提交任务时,首先申请内存空间,构建任务。然后对队列锁进行加锁,在队列尾部追加新的 BIO 任务,最后尝试唤醒正在等待任务的 BIO 线程。BIO线程启动时或持续处理完所有任务,发现任务队列为空后,就会阻塞,并等待新任务的到来。当主线程有新任务后,主线程会提交任务,并唤醒 BIO 线程。BIO 线程随后开始轮询获取新任务,并进行处理。当处理完所有 BIO 任务后,则再次进入阻塞,等待下一轮唤醒。
Redis的单线程与多线程
Redis的单线程:指Redis的网络IO和键值对读写这些对外提供键值对存储服务的主要流程都是由一个线程来完成。多线程模式面临共享资源并发访问控制问题,需要考虑同步语义保护共享资源,这会降低代码的易调试性和可维护性,为了避免这些问题,Redis采用了单线程模型
Redis快的原因:
- 纯内存KV操作,性能较高
- 单线程模型,不需要创建/销毁线程,避免上下文切换和多线程竞争
- 非阻塞的I/O多路复用机制,在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率
Redis4.0之前是单线程运行,原因如下:
- 单线程模型方便开发和调试
- 使用多路复用,单线程模型也能并发处理多客户端请求
- 对于Redis而言,主要性能瓶颈是内存或网络带宽而并非CPU
Redis4.0引入惰性/异步删除,可以使用异步方式对数据进行删除操作,如unlink key,优势是不会对主线程造成卡顿,把删除操作交给后台线程执行
Redis多线程下主线程和IO线程协作过程:
- 服务端和客户端建立Socker连接,主线程创建和客户端的连接,并把Socket放入全局等待队列,主线程通过轮训把Socket连接分配给IO线程
- 主线程一旦把Socket分配给IO线程,就进入阻塞状态,等待IO线程完成客户端请求读取和解析。多个IO线程并行处理,该过程很快完成
- IO线程解析完请求,主线程执行请求的命令操作。

4.主线程执行完请求操作后,把需要返回的结果写入缓存区,然后主线程阻塞等待IO线程把结果回写到Socket,并返回给客户端。此时也是多个IO线程并行处理,速度很快

Redis6.0多线程:核心是解决高并发场景下,单线程解析请求数据协议带来的压力。请求数据的协议解析由多线程完成之后,后面的请求处理阶段依旧还是单线程排队处理。
- 单线程的机制导致Redis的QPS很难有效提高,多线程模型可以分摊Redis同步读写I/O的压力,充分利用多核CPU资源,提高QPS。
- 所有的事件处理、读请求、命令解析、命令执行,以及最后的响应回复,都由主线程完成,一个线程处理能力有限
- 多线程是并发地进行请求命令的读取、解析,以及响应的回复。而其他的所有任务,如事件触发、命令执行、IO 任务分发,以及其他各种核心操作,仍然在主线程中进行,这些任务仍然由单线程处理
- Redis6.0默认禁用多线程,需要修改conf文件中thread-do-reads=true来开启,然后开药设置io-trheads 4,表示开启4个线程
命令处理流程中多线程的应用:
- AE事件模型中,队列不为空时,将所有请求的socket依次分配给IO线程,主线程自旋检查等待
- IO多线程开始并发处理队列任务,每个IO线程从对应列表获取一个任务,读取请求数据,进行命令解析。
- 待读取任务数变为 0,主线程循环检测,开始依次执行 IO 线程已经解析的所有命令,将响应写入client写缓冲,然后主线程将这些待回复client,轮询分配给多个IO线程,再次自旋检测等待。
- IO线程再次开始并发执行,将不同client的响应缓冲写给 client。当所有响应全部处理完后,待回复的任务数变为 0,主线程结束自旋检测,继续处理后续的任务,以及新的读请求。
多线程优劣:
- 性能虽然提升了,但是命令执行仍然在主线程进行,存在性能瓶颈
- 事件触发也是在主线程进行,无法有效使用多核心
- IO读写为批处理读写,所有IO线程先一起读完所有请求,待主线程解析处理完毕后,所有IO线程再一起回复所有响应,不同请求需要相互等待,效率不高
- IO批处理读写时,主线程自旋检测等待,效率更是低下,即便任务很少,也很容易把 CPU 打满
Redis的I/O多路复用:
- 套接字的读写默认下是阻塞的,如read操作,缓存区没有数据,线程就会阻塞直到缓冲区有数据或连接关闭,方法才返回,线程得以处理其他业务
- Redis使用非阻塞I/O,读写不再阻塞,但是存在读数据,只读取了一部分或写数据,缓存区满了数据没写完,而I/O多路复用就可以解决这个问题
- I/O多路复用最简单的实现就是使用select函数,可以监控多个文件描述符的可读和可写情况,监控到相应事件后就通知线程处理,保证了Redis读写功能的正常执行
本文来自博客园,作者:难得,转载请注明原文链接:https://www.cnblogs.com/zhangbLearn/p/18829269

浙公网安备 33010602011771号