当前位置: 首页 > news >正文

Redis之线程IO模型

引言

Redis是个单线程程序!这点必须铭记。除了Redis之外,Node.js也是单线程,Nginx也是单线程,但是他们都是服务器高性能的典范。

Redis单线程为什么能够这么快! 因为他所有的数据都在内存中,所有的运算都是内存级别的运算。正因为Redis是单线程,所以要小心使用Redis指令,对于那些时间复杂度为O(n)级别的指令,一定要谨慎使用,一不小心可能就会导致Redis卡顿。

Redis单线程如何处理那么多的并发客户端连接! 其答案就是多路复用

非阻塞IO

Redis使用基于事件的非阻塞IO模型,这种模型使得Redis能够在不阻塞主线程的情况下,同时处理多个客户端连接。Redis使用IO多路复用技术,通常是select、epoll、kqueue,这取决于运行Redis的操作系统。这些技术允许单个线程监视多个文件描述符,以检测是否有IO操作成为可能,例如数据可读或者可写。

读写过程

  • 读操作:当客户端发送命令到Redis服务器时,服务器使用非阻塞socket读取命令。命令读取完成后,单线程执行命令并处理数据
  • 写操作:处理完客户端命令后,生成的输出将通过非阻塞socket发送回客户端,如果数据无法一次性发送完毕(例如,输出缓冲区已满),剩余的数据会被缓存,并在socket可写时继续发送。

事件轮询(多路复用)

非阻塞IO有个问题,那就是线程要读数据,结果读了一部分就返回了,线程如何知道何时才应该继续读。也就是当数据到来时,线程如何得到通知,写也是一样,如果缓冲区满了,写不完,剩下的数据何时才能继续写,线程也应该得到通知。
在这里插入图片描述

事件轮询API就是用来解决这个问题的,最简单的事件轮询API是select函数,他是操作系统提供给用户程序的API。输入是读写描述符列表read_fds&write_fds, 输出是与之对应的可读可写事件。同时还提供了一个timeout参数,如果没有任何事件到来,那么就最多等待timeout时间,线程处于阻塞状态。一旦期间有任何事件到来,就可以立即返回。时间过了之后还是没有任何事件到来,也会立即返回。拿到事件后,线程就可以继续挨个处理相应的事件。处理完了继续过来轮询。于是线程就进入了一个死循环,我们把这个死循环称为事件循环,一个循环为一个周期。

每个客户端套接字socket都有对应的读写文件描述符。

read_events, write_events = select(read_fds, write_fds, timeout) 
for event in read_events:handle_read(event.fd) 
for event in write_events:handle_write(event.fd)
handle_others() # 处理其它事情,如定时任务等

因为我们通过select系统调用同时处理多个通道描述符的读写事件,因此我们将这类系统调用称为多路复用API。现代操作系统的多路复用API已经不再使用select系统调用,而改用epoll(linux)和kqueue(freebsd&macosx),因为select系统调用的性能在描述符特别多时性能会非常差。他们使用起来可能在形式上略有差异,但是本质上差不多,都可以使用上面的伪代码进行理解。

服务器套接字serversocket对象的读操作指调用accept接受客户端新连接,何时有新连接到来,也是通过select系统调用的读事件来得到通知。

指令队列

Redis会将每个客户端套接字都关联一个指令队列,客户端的指令通过队列来排队进行顺序处理,先到先服务。

响应队列

Redis同样也为每个客户端套接字关联一个响应队列。Redis服务器通过响应队列来将指令的返回结果返回给客户端。如果队列为空,那么意味着连接暂时处于空闲状态,不需要去获取写事件,也就是可以将当前的客户端描述符从write_fds里面移除,等到队列有数据了,再将描述符放进去,避免select系统调用立即返回写事件,如果发现没什么数据可以写。出这种情况的线程会飙高CPU。

定时任务

服务器除了要响应IO事件外,还要处理其他事情。比如定时任务就是非常重要的一件事,如果线程阻塞在select系统调用上,定时任务将无法得到准时调度。那么Redis是如何解决的呢。

Redis的定时任务会记录在一个称为最小堆的数据结构中,这个堆中,最快要执行的任务排在堆的最上方,在每个循环周期,Redis都会将最小堆里面已经到点的任务立即进行处理。处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是select系统调用的timeout参数。因为Redis知道未来timeout时间内,没有其他定时任务需要处理,所以可以安心睡眠timeout时间。

总结

Redis单线程,利用IO多路复用技术,单线程监听多个文件描述符,并进行事件轮询(单线程循环),轮询期间如果发生可读事件,从缓冲区中读取事件并进行处理,处理完后再将数据发送到写缓冲区写回给客户端。轮询通过select(read_fds, write_fds, timeout) 函数进行,其中timeout为轮询阻塞时间。

相关文章:

  • Redisson实现分布式锁
  • C语言 -- 宏的变长参数定义
  • kotlin lambda 表达式的原理、语法和详细用法
  • 专业纸箱厂:品质之选
  • JS 实现Date日期格式的本地化
  • Halcon 多相机统一坐标系
  • 2024年6月四六级考试复盘
  • 【Python】PySide6使用入门和注意事项
  • springboot整合sentinel接口熔断
  • 在线兴趣教学类线上学习APP应用开发部署程序组建研发团队需要准备什么?
  • js如何添加新元素到数组中
  • pytest中失败用例重跑
  • A5M2报错【列 pd.adsrc 不存在】
  • Python+Selenium自动化测试环境搭建步骤(selenium环境搭建)
  • 企业微信发送消息
  • Akka系列(七):Actor持久化之Akka persistence
  • ES6 ...操作符
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • export和import的用法总结
  • Java读取Properties文件的六种方法
  • k8s 面向应用开发者的基础命令
  • Redis字符串类型内部编码剖析
  • vue-router的history模式发布配置
  • vue的全局变量和全局拦截请求器
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • win10下安装mysql5.7
  • 产品三维模型在线预览
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 简单基于spring的redis配置(单机和集群模式)
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 如何胜任知名企业的商业数据分析师?
  • 用简单代码看卷积组块发展
  • 源码安装memcached和php memcache扩展
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 回归生活:清理微信公众号
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • ​人工智能书单(数学基础篇)
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • # 详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .NET CLR Hosting 简介
  • .NET Core 中插件式开发实现
  • .net MySql
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter