【netty系列-04】反应堆模式的种类和具体实现

news2024/11/17 23:39:15

Netty系列整体栏目


内容链接地址
【一】深入理解网络通信基本原理和tcp/ip协议https://zhenghuisheng.blog.csdn.net/article/details/136359640
【二】深入理解Socket本质和BIOhttps://zhenghuisheng.blog.csdn.net/article/details/136549478
【三】深入理解NIO的基本原理和底层实现https://zhenghuisheng.blog.csdn.net/article/details/138451491
【四】深入理解反应堆模式的种类和具体实现https://zhenghuisheng.blog.csdn.net/article/details/140113199

反应堆模式的种类和具体实现

  • 一,反应堆模式的种类和底层原理
    • 1,单线程反应堆模式
    • 2,单线程-work工作者线程池模式
    • 3,多线程主从模式
    • 4,redis中的reactor反应堆模式

一,反应堆模式的种类和底层原理

前面文章中讲解了什么是socket,socket的本质就是操作系统为我们开发人员提供的一些列api,内部封装了从客户端a 从传输层,网络层,数据链路层,物理层再到 客户端b 中的物理层,数据链路层,网络层再到传输层之间的内部协议,如下图,让开发人员秩序更加关注应用层之间的开发,不需要关注底层的具体实现

socket内部会在网络通信中,去实现 tcp的三次握手,处理丢包后的网络重传流量控制等

请添加图片描述

上一篇中讲解了Reactor反应堆模式的核心以及组成部分,接下来详细的讲解一下反应堆模式的种类以及底层的实现原理

1,单线程反应堆模式

首先第一种就是单线程的反应堆模式,就是说不管是客户端的网络连接,还是读取网络上的数据,或者说具体的相关的业务处理,都是通过一个线程里面负责和处理的

在这里插入图片描述

如下面这段代码,首先创建一个 ServerHandle 的线程任务类,并且在构造方法中设置对应的 selector 选择器和一个处理请求的 ServerSocketChannel 的通道,由于将该类作为服务端,并且为了设置是单线程的模式,因此将这个selector和socket设置为唯一

public class ServerHandle implements Runnable{
	private Selector selector;
    private ServerSocketChannel serverChannel;
    /**
     * 构造方法
     * @param port 指定要监听的端口号
     */
    public ServerHandle(int port) {
        try{
            //创建选择器
            selector = Selector.open();
            serverChannel = ServerSocketChannel.open();打开监听通道
            serverChannel.configureBlocking(false);//开启非阻塞模式
            //绑定端口 backlog设为1024
            serverChannel.socket().bind(new InetSocketAddress(port),1024);
            //监听客户端连接请求
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            started = true
        }catch(IOException e){
            e.printStackTrace();
            System.exit(1);
        }
    }
}

随后再这个类里面重写这个 run 方法,都是遍历这个selector 轮询器,获取里面的读写或者监听事件,将即将处理的事件从轮询器中删除,如果抛出异常则取消这个key的事件处理,事件全部处理完成则将轮询器close关闭

@Override
public void run() {
   //循环遍历selector
   while(started){
       try{
        //阻塞,只有当至少一个注册的事件发生的时候才会继续.
		selector.select();
           Set<SelectionKey> keys = selector.selectedKeys();
           Iterator<SelectionKey> it = keys.iterator();
           SelectionKey key = null;
           while(it.hasNext()){
               key = it.next();
               it.remove();
               try{
                   handleInput(key);
               }catch(Exception e){
                   if(key != null){
                       key.cancel();
                       if(key.channel() != null){
                           key.channel().close();
                       }
                   }
               }
           }
       }catch(Throwable t){
           t.printStackTrace();
       }
   }
   //selector关闭后会自动释放里面管理的资源
   if(selector != null){
       try{
           selector.close();
       }catch (Exception e) {
           e.printStackTrace();
       }
   }

真正处理时间的方法在这个 handleInput 方法中,里面会去判断这个key是属于哪一个事件的,比如是读事件,还是写事件,还是监听事件,都会进行相应的处理。

private void handleInput(SelectionKey key) throws IOException{
    if(key.isValid()){
        //处理新接入的请求消息
        if(key.isAcceptable()){
            //获得关心当前事件的channel
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //通过ServerSocketChannel的accept创建SocketChannel实例
            //完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
            SocketChannel sc = ssc.accept();
            System.out.println("======socket channel 建立连接=======");
            //设置为非阻塞的
            sc.configureBlocking(false);
            //连接已经完成了,可以开始关心读事件了
            sc.register(selector, SelectionKey.OP_READ);
        }
        //读消息
        if(key.isReadable()){
            System.out.println("======socket channel 数据准备完成," +
                    "可以去读==读取=======");
            SocketChannel sc = (SocketChannel) key.channel();
            //创建ByteBuffer,并开辟一个1M的缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //读取请求码流,返回读取到的字节数
            int readBytes = sc.read(buffer);
            //读取到字节,对字节进行编解码
            if(readBytes>0){
                //将缓冲区当前的limit设置为position,position=0,
                // 用于后续对缓冲区的读取操作
                buffer.flip();
                //根据缓冲区可读字节数创建字节数组
                byte[] bytes = new byte[buffer.remaining()];
                //将缓冲区可读字节数组复制到新建的数组中
                buffer.get(bytes);
                String message = new String(bytes,"UTF-8");
                System.out.println("服务器收到消息:" + message);
                //处理数据
                String result = response(message) ;
                //发送应答消息
                doWrite(sc,result);
            }
            //链路已经关闭,释放资源
            else if(readBytes<0){
                key.cancel();
                sc.close();
            }
        }
        if(key.isWritable()){
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer)key.attachment();
            if(buffer.hasRemaining()){
                int count = sc.write(buffer);
                System.out.println("write :"+count
                        +"byte, remaining:"+buffer.hasRemaining());
            }else{
                /*取消对写的注册*/
                key.interestOps(SelectionKey.OP_READ);
            }
        }
    }
}

在写事件完成之后,buffer缓冲区会有新的空间,因此可以将读到的数据写入到写缓冲区中,由于tcp全双工的特性,因此可以实现服务端边读边写的功能。

//发送应答消息
private void doWrite(SocketChannel channel,String response)
        throws IOException {
    //将消息编码为字节数组
    byte[] bytes = response.getBytes();
    //根据数组容量创建ByteBuffer
    ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
    //将字节数组复制到缓冲区
    writeBuffer.put(bytes);
    //flip操作
    writeBuffer.flip();
    channel.register(selector,SelectionKey.OP_WRITE|SelectionKey.OP_READ,
            writeBuffer);
}

通过以上案例,可以完整的说明白单线程是完全可以实现一个bio的,通过Reactor单线程反应堆的模式去完成所有的请求和连接。

虽然单线程可以完整的实现整个BIO的流程,但是单线程也有单线程的弊端,如由于业务响应也是通过单线程去处理,如果涉及到某个业务需要花费太长的时间,那么整个系统就会处于一个阻塞的状态,只有等这个任务结束之后,才能继续进行下一步的任务。因此单线程的反应堆模式也是有却缺陷的

2,单线程-work工作者线程池模式

在纯粹的单线程模式中,可能会因为某一个具体的业务导致整个系统处于一个阻塞的瘫痪状态,都是因为全部任务都共用一个线程,那么这就好办了,就是只有监听事件,读事件和写事件共用同一个单线程,如果是涉及到需要处理业务的任务,那么就将这部分丢到线程池中,通过线程池中的线程去处理,这样就不会影响主线程的执行,并且通过这种异步的方式,从而增快主线程处理任务的效率,提升整个系统的响应

在这里插入图片描述

这部分代码不做详细解释,就是再创建一个 ServerHandleWorker 的Task任务类,并且实现Callable接口,然后再这个类中重写的run方法去做对应的业务即可

3,多线程主从模式

在2中已经对1进行了很大的优化,但是在reacotr模式的线程中,还是需要处理read读事件和write写事件,因此为了让这个reactor单线程更快,那么又可以将一个线程拆分成两个线程,让主reactor只需要负责处理接收事件,让从reactor异步的去处理读事件和写事件,然后处理其他业务的事件存放在线程池中,从而完全的提升整个系统的吞吐量和效率

在这里插入图片描述

4,redis中的reactor反应堆模式

在redis的6.0之前,redis内部就是使用一个标准的单线程 reactor 反应堆模式,通过一个线程去执行连接事件,读事件,写事件和其他的一些业务事件等等

在redis6.0开始,redis内部多线程的主从模式基本一致,通过mainReactor主线程处理接收事件和读事件,但是由子线程去执行读事件和写事件,同时业务线程还是通过mainReactor主线程去执行,从而保证减少一些并发冲突

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Node端使用工作线程来解决日志开销-处理IO密集型任务

我们的BBF层很多时候会作为中间层处理后端到前端的数据&#xff0c;当然大部分时候都只是作为请求 / 响应的数据组装中心&#xff0c;但是有一个插件是怎么都绕不过去的&#xff1a;Log4js。 内部我们在Node层打印了很多日志。结果这周仔细分析了一下服务器处理请求到响应的中间…

MacOS BurpSuite安装指南

burpsuite破解 用户家目录中创建文件夹burp 两个文件&#xff1a; burp最新版jar包 burpsuite_pro_v2024.3.1.4.jar 在哪下载&#xff1f; 官网&#xff1a;Professional / Community 2024.3.1.4 | Releases 百度云盘&#xff1a;链接: 百度网盘 请输入提取码 提取码: sgsk …

线性代数|机器学习-P21概率定义和Markov不等式

文章目录 1. 样本期望和方差1.1 样本期望 E ( X ) \mathrm{E}(X) E(X)1.2 样本期望 D ( X ) \mathrm{D}(X) D(X) 2. Markov 不等式&Chebyshev不等式2.1 Markov不等式公式 概述2.2 Markov不等式公式 证明&#xff1a;2.3 Markov不等式公式 举例&#xff1a;2.4 Chebyshev不…

从架构设计的角度分析ios自带网络库和AFNetworking

总结&#xff08;先说明文章分析出的一些‘认知’&#xff09; 从本文中&#xff0c;我们可以总结出一些框架设计上的“认知”&#xff1a; 对于通用的常规配置信息方面的设计&#xff0c;我们可以通过定义一个“类似于NSURLSessionConfiguration、NSURLRequest”的类来完成设…

41、web基础和http协议

web基础与http协议 一、web web&#xff1a;就是我们所说得页面&#xff0c;打开网页展示得页面。&#xff08;全球广域网&#xff0c;万维网&#xff09; world wide webwww 分布式图形信息系统 http&#xff1a;超文本传输协议 https&#xff1a;加密的超文本传输协议…

上位机图像处理和嵌入式模块部署(mcu 项目1:固件编写)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 说完了上位机的开发&#xff0c;接下来就是固件的开发。前面我们说过&#xff0c;目前使用的开发板是极海apm32f103的开发板。它自身包含了iap示例…

人工智能--目标检测

欢迎来到 Papicatch的博客 文章目录 &#x1f349;引言 &#x1f349;概述 &#x1f348;目标检测的主要流程通常包括以下几个步骤 &#x1f34d;数据采集 &#x1f34d;数据预处理 &#x1f34d;特征提取 &#x1f34d;目标定位 &#x1f34d;目标分类 &#x1f348;…

千益畅行,旅游卡,如何赚钱?

​ 赚钱这件事情&#xff0c;只有自己努力执行才会有结果。生活中没有幸运二字&#xff0c;每个光鲜亮丽的背后&#xff0c;都是不为人知的付出&#xff01; #旅游卡服务#

【分布式数据仓库Hive】Hive的安装配置及测试

目录 一、数据库MySQL安装 1. 检查操作系统是否有MySQL安装残留 2. 删除残留的MySQL安装&#xff08;使用yum&#xff09; 3. 安装MySQL依赖包、客户端和服务器 4. MySQL登录账户root设置密码&#xff0c;密码值自定义&#xff0c;这里是‘abc1234’ 5. 启动MySQL服务 6…

element plus 日期组件中英文切换

现在的项目需要做中英文切换功能&#xff0c;我发现element plus 只有日期组件不能转换&#xff0c;然后上网查了一下并结合自己的方法写了出来。 代码&#xff1a; <template><!-- 日期框组件 --><div class"time-box">//:locale"locale&qu…

嵌入式UI开发-lvgl+wsl2+vscode系列:5、事件(Events)

一、前言 这节进行事件的总结&#xff0c;通过事件回调方式将用户和ui的交互行为绑定组合起来。 二、事件示例 1、示例1&#xff08;点击事件&#xff09; #include "../lv_examples.h" #if LV_BUILD_EXAMPLES && LV_USE_SWITCHstatic void event_cb(lv_…

解锁机器学习算法面试挑战课程

在这个课程中&#xff0c;我们将从基础知识出发&#xff0c;系统学习机器学习与算法的核心概念和实践技巧。通过大量案例分析和LeetCode算法题解&#xff0c;帮助您深入理解各种面试问题&#xff0c;并掌握解题技巧和面试技巧。无论是百面挑战还是LeetCode算法题&#xff0c;都…

华为智能驾驶方案剖析

华为ADS智驾方案始终坚持激光雷达毫米波雷达摄像头的多传感器融合路线&#xff0c;行业降本压力下硬件配置从超配逐步转向贴合实际需求&#xff0c;带动整体硬件成本下降。 1)单车传感器数量呈现下降趋势&#xff0c;包括激光雷达从3个减配至1个、毫米波雷达从6R减配至3R、摄像…

firewalld防火墙概念(形态、分类、区域)相关综合示例

目录 防火墙 概念 形态 内核态&#xff1a;netfilter 用户态&#xff1a;iptables、firewalld 防火墙分类 firewalld网络区域 区域划分 使用图形化界面配置防火墙 端口配置 协议配置 源端口配置 永久配置恢复默认防火墙规则 配置防火墙相关命令 示例 实验环境 …

谷粒商城篇章10 -- P262-P291/P295-P310 -- 订单服务(支付)【分布式高级篇七】

目录 1 页面环境搭建 1.1 静态资源上传到nginx 1.2 SwitchHosts增加配置 1.3 网关配置 1.4 订单模块基础配置 1.4.1 引入 thymeleaf 依赖 1.4.2 application.yml配置 1.4.3 bootstrap.properties配置 1.4.4 开启nacos注册发现和远程调用 1.5 修改各个页面的静态资源路…

Hadoop权威指南-读书笔记-01-初识Hadoop

Hadoop权威指南-读书笔记 记录一下读这本书的时候觉得有意思或者重要的点~ 第一章—初识Hadoop Tips&#xff1a; 这个引例很有哲理嘻嘻&#x1f604;&#xff0c;道出了分布式的灵魂。 1.1 数据&#xff01;数据&#xff01; 这一小节主要介绍了进入大数据时代&#xff0c;面…

使用简鹿音频格式转换器轻松将MP3转换为WAV音频

在音频处理领域&#xff0c;不同的格式有其特定的应用场景。有时&#xff0c;我们可能需要将MP3格式的音频转换为WAV格式&#xff0c;以满足特定的播放或编辑需求。简鹿音频格式转换器就是一款能够帮助我们轻松实现这一转换目标的工具。 为什么选择 WAV 格式&#xff1f; WAV …

CentOS中使用SSH远程登录

CentOS中使用SSH远程登录 准备工作SSH概述SSH服务的安装与启动建立SSH连接SSH配置文件修改SSH默认端口SSH文件传输 准备工作 两台安装CentOS系统的虚拟机 客户机&#xff08;192.168.239.128&#xff09; 服务器&#xff08;192.168.239.129&#xff09; SSH概述 Secure S…

【RabbitMQ实战】Springboot 整合RabbitMQ组件,多种编码示例,带你实践 看完这一篇就够了

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、对RabbitMQ管理界面深入了解1、在这个界面里面我们可以做些什么&#xff1f; 二、编码练习&#xff08;1&#xff09;使用direct exchange(直连型交换机)&a…

【深圳大学算法设计与分析】 实验六 最大流应用问题 FF -> EK -> Dinic

目录 一、实验目的&#xff1a; 二、内容&#xff1a;棒球赛问题 三、实验要求 四、提交要求 ———————— 问题分析解释&#xff1a; ———————— 算法简解&#xff1a; Ford–Fulkerson 增广 Edmonds–Karp 算法 Dinic算法 Dinic和EK的区别&#xff1a; …