Redis 线程模型

Redis 线程模型

  • 背景
  • 简介
  • Redis 单线程
    • 客户端发起 Redis 请求命令的工作原理
    • 单线程面临的挑战及问题
  • Redis 多线程
    • Redis v4.0 多线程命令
    • Redis v6.0 多线程网络模型
  • 总结

背景

随着年龄的增长,很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来,技术出身的人总是很难放下一些执念,遂将这些知识整理成文,以纪念曾经努力学习奋斗的日子。本文内容并非完全原创,大多是参考其他文章资料整理所得,感谢每位技术人的开源精神。

简介

本文介绍 Redis 线程模型的原理及演进,主要介绍 Redis 核心网络模型。

Redis 单线程

早期的 Redis 版本(准确点说是 Redis v4.0 之前的版本)使用的是单线程模型,Redis 官方给出的解释是:Redis 的瓶颈通常不会是 CPU,因为大多数请求都是 I/O 密集型而非 CPU 密集型,如果不考虑 RDB/AOF 等持久化方案,Redis 是完全的纯内存操作,执行速度非常快。Redis 真正的性能瓶颈在网络 I/O,也就是客户端与服务器之间的网络传输延迟。除此之外,单线程也可避免过多的上下文切换开销及线程同步机制开销,对 Redis 的设计、开发及维护也更加简单。
Redis 单线程模型
Redis v6.0 版本之前,核心网络模型一直是一个典型的 单 Reactor 模型,利用 select / epoll / kqueue 等 I/O 多路复用技术,在单线程事件循环中不断去处理客户端请求事件,最后回写响应数据到客户端。

核心概念:

  • client:客户端对象,Redis 是典型的 CS 架构(Client - Server),客户端通过 Socket 与服务器建立连接并发送请求命令,服务器执行请求的命令并回写响应。Redis 使用结构体 client 存储客户端所有相关信息,包括套接字连接(*conn)、当前选择的数据库指针(*db)、读入缓冲区(querybuf)、写出缓冲区(buf)、写出数据链表(reply)等。
  • aeApiPoll:I/O 多路复用 API,是基于 select / epoll_wait / kevent 等系统调用的封装,监听等待读写事件触发,然后处理,它是事件循环(Event Loop)中的核心函数,是事件驱动得以运行的基础。
  • acceptTcpHandler:连接应答处理器,底层使用系统调用 accept 接受来自客户端的新连接,并为新连接注册绑定命令读取处理器,以备后续处理新的客户端 TCP 连接;除了这个处理器,还有对应的 acceptUnixHandler 负责处理 Unix Domain Socket 以及 acceptTLSHandler 负责处理 TLS 加密连接。
  • readQueryFromClient:命令读取处理器,解析并执行客户端的请求命令。
  • beforeSleep:事件循环中进入 aeApiPoll 等待事件到来之前会执行的函数,其中包含一些日常的任务,比如把 client->buf 或者 client->reply 中的响应写回到客户端,持久化 AOF 缓冲区的数据到磁盘等,相对应的还有一个 afterSleep 函数,在 aeApiPoll 之后执行。
  • sendReplyToClient:命令回复处理器,当一次事件循环之后写出缓冲区中还有数据残留,则这个处理器会被注册绑定到相应的连接上,等连接触发写就绪事件时,它会将写出缓冲区剩余的数据回写到客户端。

Redis 内部实现了一个高性能的事件库 — AE,基于 epoll / select / kqueue / evport 四种事件驱动技术,实现 Linux / MacOS / FreeBSD / Solaris 多平台的高性能事件循环模型。Redis 的核心网络模型正式构筑在 AE 之上,包括 I/O 多路复用、各类处理器的注册绑定,都是基于此才得以运行。

客户端发起 Redis 请求命令的工作原理

  1. Redis 服务器启动,开启主线程事件循环(Event Loop),注册 acceptTcpHandler 连接应答处理器到用户配置的监听端口对应的文件描述符,等待新连接带来;
  2. 客户端和服务器建立网络连接;
  3. accpetTcpHandler 被调用,主线程使用 AE 的 API 将 readQueryFromClient 命令读取处理器绑定到新连接对应的文件描述符上,并初始化一个 client 绑定这个客户端连接;
  4. 客户端发送请求命令,触发读就绪事件,主线程调用 readQueryFromClient 通过 Socket 读取客户端发送过来的命令存入 client->querybuf 读入缓冲区;
  5. 调用 processInputBuffer,在其中使用 processInlineBuffer 或者 processMultibulkBuffer 根据 Redis 协议解析命令,最后调用 processCommand 执行命令;
  6. 根据请求命令的类型(SET / GET / DEL / EXEC / …),分配响应的命令执行器去执行,最后调用 addReply 函数族的一系列函数将响应数据写入到对应 client 的写出缓冲区:client->buf 或者 client->replyclient->buf 是首选的写出缓冲区,固定大小16KB,一般来说可以缓冲足够多的响应数据,但是如果客户端在时间窗口内需要响应的数据非常大,那么则会自动切换到 client->reply 链表上去,使用链表理论上能够保存无限大的数据(受限于机器的物理内存),最后把 client 添加进一个 LIFO 队列 clients_pending_write
  7. 在事件循环(Event Loop)中,主线程执行 beforeSleep -> handleClientsWithPendingWrites,遍历 clients_pending_write 队列,调用 writeToClientclient 的写出缓冲区理的数据回写到客户端,如果写出缓冲区还有数据遗留,,则注册 sendReplyToClient 命令回写处理器到该连接的写就绪事件,等待客户端可写时在事件循环中再继续回写残余的响应数据。

单线程面临的挑战及问题

Redis 中存在一些非常耗时的命令,这些命令的执行可能会阻塞单线程的事件循环,譬如 Redis 的 DEL 命令可用于删除一个或多个 key 存储的值,这是个阻塞命令,大多数情况下需要一次性删除的 key 中存储的值不会特别多,但如果遇到需要删除一个超大键值对的场景时,DEL 命令执行可能会阻塞,又因为事件循环是单线程的,所以也会同步阻塞后续的其他事件,导致吞吐量下降。

Redis 多线程

Redis v4.0 多线程命令

Redis v4.0 版本引入多线程,将一些可能耗时阻塞单线程事件循环的命令执行异步化,增加了一些非阻塞命令,如:UNLINKFLUSHALL ASYNCFLUSHDB ASYNC。其中 UNLINK 命令其实就是 DEL 的异步版本,UNLINK 不会同步删除数据,知识将 key 从 keyspace 中暂时移除,然后将任务添加到一个异步队列,最后由后台线程去删除,但使用 UNLINK 去删除一个很小的 key 时开销反而更大,所以删除前会先计算一个开销阈值,只有当这个值大于 64 才会使用异步删除,对于基本数据类型,如 ListSetHash,阈值就是其中存储的对象数量。

不过需要注意的是,Redis v4.0 的核心网络模型仍然是单线程的。

Redis v6.0 多线程网络模型

Redis v6.0 版本开始在核心网络模型中引入多线程,原因是 Redis 的网络 I/O 瓶颈已愈加明显。Redis 单线程模式导致系统消耗很多 CPU 时间在网络 I/O 上,从而降低了吞吐量,要提升 Redis 性能可以考虑两个方向:

  • 优化网络 I/O 模块
  • 提高机器内存读写速度(依赖于硬件发展)

Redis v6.0 版本引入了 I/O Threading 多线程,利用多核的优势优化网络 I/O。

Redis 多线程模型

  1. Redis 服务器启动,开启主线程事件循环(Event Loop),注册 acceptTcpHandler 连接应答处理器到用户配置的监听端口对应的文件描述符,等待新连接请求;
  2. 客户端和服务器建立网络连接;
  3. acceptTcpHandler 被调用,主线程使用 AE 的 API 将 readQueryFromClient 命令读取处理器绑定到新连接对应的文件描述符上,并初始化一个 client 绑定这个客户端连接;
  4. 客户端发送请求命令,触发读就绪事件,服务器主线程不会通过 Socket 去读取客户端的请求命令,而是先将 client 放入一个 LIFO 队列 clients_pending_read
  5. 在事件循环(Event Loop)中,主线程执行 beforeSleep -> handleClientsWithPendingReadsUsingThreads,利用 Round-Robin 轮询负载均衡策略,把 clients_pending_read 队列中的连接均匀地分配给 I/O 线程各自的本地 FIFO 任务队列 io_threads_list[id] 和主线程自己,I/O 线程通过 Socket 读取客户端的请求命令,存入 client->querybuf 并解析第一个命令,但不执行命令,主线程忙轮询,等待所有 I/O 线程完成读取任务;
  6. 主线程和所有 I/O 线程都完成了读取任务,主线程结束忙轮询,遍历 clients_pending_read 队列,执行所有客户端连接的请求命令,先调用 processCommandAndResetClient 执行第一条已经解析好的命令,然后调用 processInputBuffer 解析并执行客户端连接的所有命令,在其中使用 processInlineBuffer 或者 processMultibulkBuffer 根据 Redis 协议解析命令,最后调用 processCommand 执行命令;
  7. 根据请求命令的类型(SET / GET / DEL / EXEC / …),分配相应的命令执行器去执行,最后调用 addReply 函数族的一系列函数将响应数据写入到对应 client 的写出缓冲区:client->buf 或者 client->replyclient->buf 是首选的写出缓冲区,固定大小 16 KB,一般来说可以缓冲足够多的响应数据,但是如果客户端在时间窗口内需要响应的数据非常大,那么则会自动切换到 client->reply 链表上去,使用链表理论上能够保存无限大的数据(受限于机器的物理内存),最后把 client 添加进一个 LIFO 队列 clients_pending_write
  8. 在事件循环(Event Loop)中,主线程执行 beforeSleep -> handleClientsWithPendingWritesUsingThreads,利用 Round-Robin 轮询负载均衡策略,把 clients_pending_write 队列中的连接均匀地分配给 I/O 线程各自的本地 FIFO 任务队列 io_threads_list[id] 和主线程自己,I/O 线程通过调用 writeToClientclient 的写出缓冲区里的数据回写到客户端,主线程忙轮询,等待所有 I/O 线程完成写出任务;
  9. 主线程和所有 I/O 线程都完成了写出任务, 主线程结束忙轮询,遍历 clients_pending_write 队列,如果 client 的写出缓冲区还有数据遗留,则注册 sendReplyToClient 到该连接的写就绪事件,等待客户端可写时在事件循环中再继续回写残余的响应数据。

总结

  • Redis v6.0 版本之前的核心网络模型都是单线程 ;
  • Redis v4.0 为解决一些耗时命令阻塞单线程事件循环的问题,引入多线程并增加了一些非阻塞命令,但此时核心网络模型仍然为单线程;
  • Redis v6.0 版本开始,核心网络模型使用多线程,以解决网络 I/O 方面的瓶颈问题;
  • Redis 单线程模型和多线程模型的区别在于,多线程模型把读取客户端请求命令和回写响应数据的逻辑异步化,交给 I/O 线程去完成,但是 I/O 线程仅负责读取和解析客户端命令,并不会真正去执行命令,客户端的命令执行最终还是在主线程上完成的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/340888.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LangChain学习之 Question And Answer的操作

1. 学习背景 在LangChain for LLM应用程序开发中课程中,学习了LangChain框架扩展应用程序开发中语言模型的用例和功能的基本技能,遂做整理为后面的应用做准备。视频地址:基于LangChain的大语言模型应用开发构建和评估。 2. Q&A的作用 …

了解VS安全编译选项GS

缓冲区溢出攻击的基本原理就是溢出时覆盖了函数返回地址,之后就会去执行攻击者自己的函数; 针对缓冲区溢出时覆盖函数返回地址这一特征,微软在编译程序时使用了安全编译选项-GS; 目前版本的Visual Studio中默认启用了这个编译选项…

Java-----String类

1.String类的重要性 经过了C语言的学习,我们认识了字符串,但在C语言中,我们表示字符串进行操作的话需要通过字符指针或者字符数组,可以使用标准库中提供的一系列方法对字符串的内容进行操作,但这种表达和操作数据的方…

Go语言交叉编译

Golang 支持交叉编译, 在一个平台上生成然后再另外一个平台去执行。 以下面代码为例 build ├── main.go ├── go.mod main.go内容 package mainimport "fmt"func main() {fmt.Println("hello world") }windows系统上操作 1.cmd窗口编译…

【OCPP】ocpp1.6协议第4.2章节BootNotification的介绍及翻译

目录 4.2、BootNotification-概述 Boot Notification 消息 BootNotification 请求消息 BootNotification 响应消息 使用场景 触发 BootNotification 的条件 实现示例 构建请求消息 发送请求并处理响应 小结 4.2、BootNotification-原文译文 4.2.1、被中央系统接受之…

ios v品会 api-sign算法

vip品会 api-sign算法还原 ios入门案例 视频系列 IOS逆向合集-前言哔哩哔哩bilibili 一、ios难度与安卓对比 这里直接复制 杨如画大佬的文章的内容: ios难度与安卓对比 很多人说ios逆向比安卓简单,有以下几个原因 1 首先就是闭源,安卓开源…

无人售货机零售业务成功指南:从市场分析到创新策略

在科技驱动的零售新时代,无人售货机作为一种便捷购物解决方案,正逐步兴起,它不仅优化了消费者体验,还显著降低了人力成本,提升了运营效能。开展这项业务前,深入的市场剖析不可或缺,需聚焦消费者…

ch4网络层---计算机网络期末复习(持续更新中)

网络层概述 将分组从发送方主机传送到接收方主机 发送方将运输层数据段封装成分组 接收方将分组解封装后将数据段递交给运输层网络层协议存在于每台主机和路由器上 路由器检查所有经过它的IP分组的分组头 注意路由器只有3层(网络层、链路层、物理层) 网络层提供的服务 一…

discuz如何添加主导航

大家好,今天教大家怎么样给discuz添加主导航。方法其实很简单,大家跟着我操作既可。一个网站的导航栏是非常重要的,一般用户进入网站的第一印象就是看网站的导航栏。如果大家想看效果的话可以搜索下网创有方,或者直接点击查看效果…

SpringCloud Feign用法

1.在目标应用的启动类上添加开启远程fein调用注解: 2.添加一个feign调用的interface FeignClient("gulimall-coupon") public interface CouponFeignService {PostMapping("/coupon/spubounds/save")R save(RequestBody SpuBondTo spuBounds);…

C++语言学习(七)—— 继承、派生与多态(一)

目录 一、派生类的概念 1.1 定义派生类的语法格式 1.1.1 定义单继承派生类 1.1.2 定义多继承派生类 1.2 继承方式 二、公有继承 三、派生类的构造和析构 四、保护成员的引入 五、改造基类的成员函数 六、派生类与基类同名成员的访问方式 七、私有继承和保护继承 7.…

zdppy_api 中间件请求原理详解

单个中间件的逻辑 整体执行流程: 1、客户端发起请求2、中间件拦截请求,在请求开始之前执行业务逻辑3、API服务接收到中间件处理之后的请求,和数据库交互,请求数据4、数据库返回数据5、API处理数据库的数据,然后给客户…

探索数据结构:快速排序与归并排序的实现与优化

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:数据结构与算法 贝蒂的主页:Betty’s blog 1. 快速排序 1.1. 算法思想 **快速排序(Quick Sort)**是Hoare于1962年…

【数据结构】穿梭在二叉树的时间隧道:顺序存储的实现

专栏引入 哈喽大家好,我是野生的编程萌新,首先感谢大家的观看。数据结构的学习者大多有这样的想法:数据结构很重要,一定要学好,但数据结构比较抽象,有些算法理解起来很困难,学的很累。我想让大家…

AI程序员来了,大批码农要失业

根据GitHub发布的《Octoverse 2021年度报告》,2021年中国有755万程序员,排名全球第二。 ChatGPT的出现,堪比在全球互联网行业点燃了一枚“核弹”,很多人都会担心“自己的工作会不会被AI取代”。 而2024年的AI进展速度如火箭般&am…

getway整合sentinel流控降级

3. 启动sentinel控制台增加流控规则: 根据API分组进行流控: 1.设置API分组: 2.根据API分组进行流控: 自定义统一异常处理: nginx负载配置:

vue-router 源码分析——2. router-link 组件是如何实现导航的

这是对vue-router 3 版本的源码分析。 本次分析会按以下方法进行: 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时…

德人合科技——@天锐绿盾 | -文档透明加密系统

天锐绿盾文档透明加密系统是一种先进的数据安全解决方案,旨在保护企业和组织的敏感信息,防止未经授权的访问和泄漏。 PC地址: https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是该系统的一些关键特点和功…

【读书笔记】曼陀罗思考法

目录 1 起源2 路径示例——人生规划设计 3 分类3.1 扩展型“扩展型”曼陀罗——使用方法 3.2 围绕型 4 注意事项 1 起源 曼陀罗在梵文中意味着“圣地”,象征着宇宙的秩序和内心的神圣结构。 “曼陀罗思考法”,是由日本学者今泉浩晃发明的方法&#xff…

【计算机毕设】基于SpringBoot的中小企业设备管理系统设计与实现 - 源码免费(私信领取)

免费领取源码 | 项目完整可运行 | v:chengn7890 诚招源码校园代理! 1. 研究目的 在中小企业中,设备管理是确保生产和运营效率的重要环节。传统的设备管理通常依赖于手工记录和人工管理,容易导致数据不准确、…