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. Github带来的不止是开源,还有折叠的认知

    如果第二次看到我的文章,欢迎右侧扫码订阅我哟~ 

  2. 利用电脑开启自带虚拟wifi,无需第三方工具。

    注:此方法只验证win 7以上系统,XP  server 2016 系统未验证 1.新建记事本,在记事本中输入netsh wlan set hostednetwork mode=allow ssid= ...

  3. 深度讲解Linux内存管理和Linux进程调度-打通任督二脉

    我在多年的工程生涯中发现很多工程师碰到一个共性的问题:Linux工程师很多,甚至有很多有多年工作经验,但是对一些关键概念的理解非常模糊,比如不理解CPU.内存资源等的真正分布,具体的工作机制,这使得他 ...

  4. Java基础(三十一)JDBC(1)常用类和接口

    1.Driver接口 每种数据库的驱动程序都应该提供一个实现java.sql.Driver接口的类.在加载某一驱动程序的Driver类时,它应该创建自己的实例并向java.sql.DriverMana ...

  5. 关于./xhost: unable to open display问题的解决

    看了很多大同小异的帖子,都没能解决这个问题,以下是我的实测经验,注意第三步,很关键. 注:以下操作在确保vncserver.xdpyinfo服务正常的情况下进行 第一步:root登录,启动vncser ...

  6. Django的下载与基本命令

    1.下载Django: pip3 install django==2.1.2 2.创建一个django project django-admin startproject 项目名称 3.在项目目录下创 ...

  7. vue引入css文件报错Unrecognised input

    一个vue项目中用到了swiper插件,引入swiper.css时报错 显示引入的css文件Unrecognised input ,在文件的line4,column12 . 其实是引入位置不对,样式文 ...

  8. Centos7 基础命令与软件的安装

    本人小白一枚正在老男孩培训,所以从现在开始把我学到的知识都分享给大家,该随笔会一直更新 centos7基础命令与软件 ps:命令与参数之间必须加上空格,安装成功时最后一行会有  Complete!   ...

  9. 基于SkyWalking的分布式跟踪系统 - 微服务监控

    上一篇文章我们搭建了基于SkyWalking分布式跟踪环境,今天聊聊使用SkyWalking监控我们的微服务(DUBBO) 服务案例 假设你有个订单微服务,包含以下组件 MySQL数据库分表分库(2台 ...

  10. Java编程思想——第17章 容器深入研究(two)

    六.队列 排队,先进先出.除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: add ...