1. Redis的网络模型

Redis基于Reactor模式(反应堆模式)开发了自己的网络模型,形成了一个完备的基于IO复用的事件驱动服务器,但是不由得浮现几个问题:

  • 为什么要使用Reactor模式呢?
  • Redis如何实现自己的Reactor模式?

2. Reactor模式的背景

单纯的epoll/kqueue可以单机支持数万并发,单纯从性能的角度而言毫无问题,但是技术实现和软件设计仍然存在一些差异。设想这样一种场景:

  • epoll/kqueue将收集到的可读写事件全部放入队列中等待业务线程的处理,此时线程池的工作线程拿到任务进行处理,实际场景中可能有很多种请求类型,工作线程每拿到一种任务就进行相应的处理,处理完成之后继续处理其他类型的任务;
  • 工作线程需要关注各种不同类型的请求,对于不同的请求选择不同的处理方法,因此请求类型的增加会让工作线程复杂度增加,维护起来也变得越来越困难;

作为吃货的你,如果还是没听懂,不用着急,想象一下吃饭的场景:

城南大熊饭店在来客高峰期服务员快速地接待了很多顾客,有的顾客点凉菜,有的点热菜,有的点主食,有的点饮料等等,在后厨如果人员没有分工,那么厨师A一会儿弄凉菜一会弄主食,厨师B一会儿弄热菜一会儿弄饮料,厨师C一会儿弄主食一会儿弄火锅.....虽然最终也响应了顾客的需求,但是把这帮厨师累够呛并且厨师都是全栈厨师(FullStack),对饭店来说招个这样的厨师非常不容易而且薪水也高,老板孙大熊很苦恼。

当局者迷旁观者清,孙大熊找到了他MBA的同学李明诉说这个苦恼,李明说专业的人做专业的事情才是王道,凉菜的有凉菜厨师、热菜有热菜厨师、主食有主食厨师,如果有新增的菜系菜品,那就再找个专门做这个菜品的厨师就可以了,这样解决了厨师业务能力要求高、饭店扩张慢的问题,孙大熊频频点头,感觉自己花钱买的这个MBA算是瞎了,回去之后进行改进,果然有很大的改善,孙大熊又开始嘚瑟了。

上面的场景其实和高并发网络模型很相似,如果我们在epoll/kqueue的基础上进行业务区分,并且对每一种业务设置相应的处理函数,每次来任务之后对任务进行识别和分发,每种处理函数只处理一种业务,这种模型更加符合OO的设计理念,这也是Reactor反应堆模式的设计思路。

3. Reactor模式

基于Reactor的组件阵营非常强大:

  • Java NIO
  • Netty
  • libevent/libuv
  • Redis

反应堆模式是一种对象行为的设计模式,主要同于同步IO,异步IO有Proactor模式,这里不详细讲述Proactor模式,

二者的主要区别就是Reactor是同步IO,Proactor是异步IO,理论上Proactor效率更高,但是Proactor模式需要操作系统在内核层面对异步IO进行支持,Linux的Boost.asio就是Proactor模式的代表,Windows有IOCP。

网上比较经典的一张Reactor模式的类图:

图中给出了5个部件分别为:

  • handle 可以理解为读写事件 可以注册到Reactor进行监控
  • Sync event demultiplexer 可以理解为epoll/kqueue/select等作为IO事件的采集器
  • Dispatcher 提供注册/删除事件并进行分发,作为事件分发器
  • Event Handler 事件处理器 完成具体事件的回调 供Dispatcher调用
  • Concrete Event Handler 具体请求处理函数

更简洁的流程如下:

以网络场景为例:

循环前先将待监控的事件进行注册,当监控中的Socket读写事件到来时,事件采集器epoll等IO复用工具检测到并且将事件返回给事件分发器Dispatcher,

分发器根据读、写、异常等情况进行分发给事件处理器,事件处理器进而根据事件具体类型来调度相应的实现函数来完成任务。

4. Redis的Reactor实现

Redis处理客户端业务(文件事件)的基本流程:

  • Redis的IO复用的选择
 #ifdef HAVE_EVPORT
 #include "ae_evport.c"
 #else
     #ifdef HAVE_EPOLL
     #include "ae_epoll.c"
     #else
         #ifdef HAVE_KQUEUE
         #include "ae_kqueue.c"
         #else
         #include "ae_select.c"
         #endif
     #endif
 #endif

Redis中支持多种IO复用,源码中使用相应的宏定义进行选择,编译时就可以获取当前系统支持的最优的IO复用函数来使用,从而实现了Redis的优秀的可移植特性。

  • Redis的任务事件队列

由于Redis的是单线程处理业务的,因此IO复用程序将读写事件同步的逐一放入队列中,如果当前队列已经满了,那么只能出一个入一个,

但是由于Redis正常情况下处理得很快,不太会出现队列满迟迟无法放任务的情况,但是当执行某些阻塞操作时将导致长时间的阻塞,无法处理新任务。

  • Redis事件分派器

事件的可读写是从服务器角度看的,分派看到的事件类型包括:

  • AE_READABLE 客户端写数据、关闭连接、新连接到达
  • AE_WRITEABLE 客户端读数据

特别地,当一个套接字连接同时可读可写时,服务器会优先处理读事件再处理写事件,也就是读优先。

  • Redis事件处理器

Redis将文件事件进行归类,编写了多个事件处理器函数,其中包括:

  • 连接应答处理器:实现新连接的建立
  • 命令请求处理器:处理客户端的新命令
  • 命令回复处理器:返回客户端的请求结果
  • 复制处理器:实现主从服务器的数据复制

当然还有其他的事件处理函数,但是只要的是这四种。

  • Redis C/S一次完整的交互

Redis服务器的主线程处于循环中,此时一个Client向Redis服务器发起连接请求,假如是6379端口,监听端口在IO复用工具下检测到AE_READABLE事件,并将该事件放入TaskQueue中,等待被处理,事件分派器获取这个读事件,进一步确定是新连接请求,就将该事件交给了连接应答处理器建立连接;建立连接之后Client继续向服务器发送了一个get命令,仍然被IO复用检测处理放入队列,被事件分派器处理指派给命令请求处理器,调用相应程序进行执行;服务器将套接字的AE_WRITEABLE事件与命令回复处理器相关联,当客户端尝试读取结果时产生可写事件,此时服务器端触发命令回复响应,并将数据结果写入套接字,完成之后服务端接触该套接字与命令回复处理器之间的关联;

理解Redis的反应堆模式的更多相关文章

  1. 理解Redis单线程运行模式

    本文首发于:https://mp.weixin.qq.com/s/je4nqCIq6ARhSV2V5Ymmtg 微信公众号:后端技术指南针 0.概述 通过本文将了解到以下内容: Redis服务器采用单 ...

  2. 理解Redis的单线程模式

    0.概述 本文基于的Redis版本为4.0以下,在Redis更高版本中并不是完全的单线程了,增加了BIO线程,本文主要讲述主工作线程的单线程模式. 通过本文将了解到以下内容: Redis服务器采用单线 ...

  3. 反应堆模式最牛的那篇论文--由solidmango执笔翻译

    The Reactor:An Object-Oriented Wrapper for Event-Driven Port Monitoring and Service Demultiplexing 反 ...

  4. Redis与Reactor模式

    Redis与Reactor模式 Jan 9, 2016 近期看了Redis的设计与实现,这本书写的还不错,看完后对Redis的理解有非常大的帮助. 另外,作者整理了一份Redis源代码凝视,大家能够c ...

  5. Redis集群模式介绍

    前言: 一.为什么要使用redis 1,解决应用服务器的cpu和内存压力 2,减少io的读操作,减轻io的压力(内存中读取) 3,关系型数据库扩展性,不强,难以改变表的结构 二.优点 1,nosql数 ...

  6. 5分钟实现用docker搭建Redis集群模式和哨兵模式

    如果让你为开发.测试环境分别搭一套哨兵和集群模式的redis,你最快需要多久,或许你需要一天?2小时?事实是可以更短. 是的,你已经猜到了,用docker部署,真的只需要十几分钟. 一.准备工作 拉取 ...

  7. MVC+EF 理解和实现仓储模式和工作单元模式

    MVC+EF 理解和实现仓储模式和工作单元模式 原文:Understanding Repository and Unit of Work Pattern and Implementing Generi ...

  8. 深入理解JavaScript中创建对象模式的演变(原型)

    深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...

  9. 深入理解Redis:底层数据结构

    简介 redis[1]是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorte ...

随机推荐

  1. UNCTF杂项题Hidden secret 之NTFS交换数据流隐写

    ---恢复内容开始--- 做这道题目的经历比较坎坷,题目中用于隐藏flag的jpg文件出了问题,导致不能被交换数据流隐写所以出题人换了一次题目,最后做法也换了,不过出题人一开始的考察点还是基于NTFS ...

  2. ESP8266开发之旅 网络篇⑨ HttpClient——ESP8266HTTPClient库的使用

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  3. springboot security+redis+jwt+验证码 登录验证

    概述 基于jwt的token认证方案 验证码 框架的搭建,可以自己根据网上搭建,或者看我博客springboot相关的博客,这边就不做介绍了.验证码生成可以利用Java第三方组件,引入 <dep ...

  4. 微服务架构 ------ Day01 微服务架构优缺点

    1. 微服务架构的优点 庞大的单体程序 -> 一套微型程序. 每一个服务有明确的边界(服务之间的消息通讯机制) ,每一个服务都能单独的开发和维护,并且更好理解 每一个服务都能由一个团队来开发,当 ...

  5. Spring事务传播属性有那么难理解吗?

    学习东西要知行合一,如果只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来我们一起实践来学习Spring事务的传播属性. 传播属性 传播属性定义的是当一个事务方法碰到另一个事 ...

  6. Spring Cloud - Eureka /actuator/info 如何显示信息

    在pom.xml中添加 <!-- actuator监控信息完善 --> <dependency> <groupId>org.springframework.boot ...

  7. Kafka权威指南阅读笔记(第五章)

    Kafka Broker kafka 第一个启动的Broker在ZooKeeper中创建一个临时节点/controller,让自己成为控制器.其他Broker启动后在控制器节点上创建Watch对象,便 ...

  8. jQuery选择器 大于 空格 波浪线 加号

    JQuery选择器 大于 空格 波浪线 加号的区别 元素遍历 符号 说明 空格 $(‘parent child’)表示获取parent下的所有的child节点(所有的子孙). 大于号 $(‘paren ...

  9. vue-socket.io 及 egg-socket.io 的简单使用

    egg-socket.io 的使用 官方文档看这里 egg-socket.io 接下来的内容其实与文档里差不多,介意的童鞋略过就好,目前只是简单的引入,下周往后会写复杂些的逻辑,在后面的文章会介绍. ...

  10. IDEA上tomcat的配置

    IDEA上tomcat的配置   IDEA上集成自己的tomcat,主要就是下面这张表的配置,不累述.