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. ESP8266开发之旅 基础篇① 走进ESP8266的世界

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

  2. 百万年薪python之路 -- 面向对象之 反射,双下方法

    面向对象之 反射,双下方法 1. 反射 计算机科学领域主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省) python面向对象中的反射:通过字符串的形式操作对象相关的属性.python ...

  3. 收藏收藏:时隔一年,你关注的打造一个实用的TXT文本操作及日志框架,我们开源了,不再为程序写日志发愁(也支持.net core哦)

    记得做这个框架是在2018年刚接触.net core的时候,那个时候为了能够专心的研究我开始不写博客了,但是学有所成并在公司运用了近一年的时间了,决定回来和各位分享我们所掌握的那星星点点的知识,希望可 ...

  4. python3.x以上 爬虫 使用问题 urllib(不能使用urllib2)

    问题一: python 3.x 以上版本揽括了 urllib2,把urllib2 和 urllib 整合到一起. 并且引入模块变成一个,只有 import urllib # import urllib ...

  5. day27作业

    FTP需求 客户端可以注册登录 client:输入一个login sever:执行login 客户端登陆后可以选择文件夹上传与下载 client:输入一个upload,download sever:执 ...

  6. fenby C语言 P19

    #include <stdio.h> int main(){ int i,j; for(i=1;i<=8;i++) { for(j=1;j<=i;j++) { printf(& ...

  7. arango集群部署

    arango集群部署 ############arango集群操作################## arangodb3-3.3.16-1.x86_64.rpm(使用rpm包方式安装) arango ...

  8. python 线程、进程与协程

    一.什么是线程?什么是进程? 第一,进程是一个实体.每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region).数据区域(data region)和堆栈(stack regio ...

  9. JVM三部曲之运行时数据区 (第一部)

    在接下来的几天想总结下,JVM相关的一些内容,比如下面的这三个内容算是比较核心知识点了 1.运行时数据区域: 在运行时数据区里存储类Class文件元数据(方法区),对象和数组(堆),方法参数局部变量( ...

  10. 【Maven学习笔记】mvn help:system 命令的说明

    mvn help:system 命令的说明 笔者用得是windows 10 x64系统 下载了Maven3,正确配置了系统变量M2_HOME的值,并且添加到Path变量路径当中. 简单来说,Maven ...