Reactor 模式
事件驱动模型
Netty是 一个异步事件驱动的网络应用程序框架, 用于快速开发可维护的高性能协议服务器和客户端。
事件驱动模型主要应用在图形用户界面(GUI)、网络服务和 Web 前端上。
比如编写图形用户界面程序, 要给界面上每个按钮都添加监听函数, 而该函数只有在相应的按钮被用户点击的事件发生时才会执行, 开发者并不需要事先确定事件何时发生, 只需要编写事件的响应函数即可。监听函数或者响应函数就是所谓的事件处理器(event handler), 类似的事件还有鼠标移动、按下、松开、双击等等, 这就是事件驱动。
事件驱动的程序一般都有一个主循环(main loop)或称事件循环(event loop), 该循环不停地做两件事: 事件监测和事件处理。首先要监测是否发生了事件, 如果有事件发生则调用相应的事件处理程序, 处理完毕再继续监测新事件。
Reactor 是什么
关于 reactor 是什么
-
事件驱动(event handling)
-
可以处理一个或多个输入源(one or more inputs)
-
通过 Service Handler 同步的将输入事件(Event)采用多路复用分发给相应的 Request Handler(多个)处理
只提供一个阻塞对象service handler(reactor)

常见的网络服务中,如果每一个客户端都维持一个与登陆服务器的连接。那么服务器将维护多个和客户端的连接以出来和客户端的 contnect 、read、write ,特别是对于长链接的服务,有多少个 c 端,就需要在 s 端维护同等的 IO 连接。这对服务器来说是一个很大的开销。
单 Reactor 单线程模型

单 Reactor 多线程模型
Reactor对象通过select()监控客户端请求事件,收到事件后,通过dispatch()进行分发。worker线程池会分配独立线程完成真正的业务,并将结果返回给handler。
优点:可以充分利用多核cpu的处理能力。
缺点是reactor处理所有的事件监听和响应,此reactor是单线程运行,在高并发场景容易出现性能瓶颈。
主从Reactor 多线程模型
Reactor主线程只处理连接请求
Reactor子线程可以有多个,每一个去处理io的读取和业务处理
优点1:父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理。
优点2:父线程与子线程的数据交互简单,Reactor主线程只需要把新连接传给子线程,子线程无需返回数据。
Netty模型
简单版:
进阶版:
bossgroup中的每个线程
workgroup中的每个线程
pipeline
Netty组件入门
ChannelInitializer
创建一个 ChannelInitializer(通道初始化),这里主要就是管理自定义 Handler,最终把这些 Handler 组装成一条双向链表,Channel 有事件时则触发链表进行业务处理逻辑

ChannelPipeline
ChannelPipeline 可认为是一个管道,是管理业务 Handler,通俗理解是保存 ChannelHandler 的 List 集合,负责编排ChannelHandler以使ChannelHandler能有效的协同作业
ChannelPipeline 底层设计是采用责任链设计模式,作用是管理 Handler 双向链表,包括入站和出站,主要是拦截 inboud 和 outbound 事件,然后每个 handler 节点负责处理具体逻辑
ChannelHandler
ChannelHandler: 在Netty中作为处理Channel中的事件以及数据的一种方式。
对于入站与出站消息又分别使用ChannelInboundHandler与ChannelOutboundHandler来处理
- ChannelOutboundHandler:处理出站数据并且允许拦截所有的操作
- ChannelInboundHandler:处理入站数据以及各种状态的变化
ChannelInboundHandler 的实现类 ChannelInboundHandlerAdapter 处理入站 I/O 事件
ChannelOutboundHandler 的实现类 ChannelOutboundHandlerAdapter 处理出站 I/O 事件
ChannelHandler生命周期:
| 类型 | 描述 |
|---|---|
| handlerAdded | 当把ChannelHandler添加到ChannelPipeline中时被调用 |
| handlerRemoved | 当把ChannelHandler在ChannelPipeline中移除时调用 |
| exceptionCaught | 当ChannelHandler在处理过程中出现异常时调用 |
ChannelInboundHandler
ChannelInboundHandler中可以获取网络数据并处理各种事件,列出能处理的各种事件
入站事件是被动接收事件,例如接收远端数据,通道注册成功,通道变的活跃等等。
事件 说明 channelRegistered 当Channel注册到它的EventLoop并且能够处理I/O时调用 channelUnregistered 当Channel从它的EventLoop中注销并且无法处理任何I/O时调用 channelActive 当Channel处理于活动状态时被调用 channelInactive 不再是活动状态且不再连接它的远程节点时被调用 channelReadComplete 当Channel上的一个读操作完成时被调 channelRead 当从Channel读取数据时被调用 channelWritabilityChanged 当Channel的可写状态发生改变时被调用 userEventTriggered 当ChannelInboundHandler.fireUserEventTriggered()方法被调用时触发 Netty另外还提供了一个类来简化这一过程SimpleChannelInboundHandler类,
@Sharable// 表示它可以被多个channel安全地共享。在实际运行时,是起不到什么作用的 public class ClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.err.println(msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }继承SimpleChannelInboundHandler后,处理入站的数据我们只需要重新实现channelRead0()方法。
当channelRead真正被调用的时候我们的逻辑才会被处理。这里使用的是模板模式,让主要的处理逻辑保持不变,让变化的步骤通过接口实现来完成
ChannelOutboundHandler
出站的数据和操作由ChannelOutboundHandler接口处理
出站事件是主动触发事件,例如绑定,注册,连接,断开,写入等等。ChannelOutboundHandler定义的方法列出
方法 描述 bind 当请求将Channel绑定到本地地址时被调用 connet 当请求将Channel连接到远程节点时被调用 disconnect 当请求将Channel从远程节点断开时调用 close 当请求关闭Channel时调用 deregister 当请求将Channel从它的EventLoop注销时调用 read 当请求从Channel中读取数据时调用 flush 当请求通过Channel将入队数据冲刷到远程节点时调用 write 当请求通过Channel将数据写入远程节点时被调用
ChannelHandlerContext
ChannelHandlerContext 是对 ChannelHandler 的封装。
当一个 ChannelHandler 添加到管道ChannelPipeline时,由ChannelPipeline创建一个包裹 ChannelHandler 的上下文ChannelHandlerContext对象添加进去。
在
ChannelHandler中可以得到这个上下文对象ChannelHandlerContext,这样它就可以:可以向上游或下游传递事件,实现责任链的功能,将事件传递给下一个处理器
ChannelHandler处理。
责任传播和终止机制:以 ChannelInboundHandlerAdapter 的 channelRead 方法为例
ChannelHandlerContext 会默认调用 fireChannelRead 方法将事件默认传递到下一个处理器。
如果我们重写了 ChannelInboundHandlerAdapter 的 channelRead 方法,并且没有调用 fireChannelRead 进行事件传播,那么表示此次事件传播已终止。
ChannelHandlerContext 负责保存责任链节点上下文信息
首先ChannelHandlerContext是一个AttributeMap,可以用来存储多个数据。
可以从ChannelHandlerContext中获取到对应的channel,handler和pipline:
Channel channel();
ChannelHandler handler();
ChannelPipeline pipeline();
Channel
Channel:Netty 的网络通信组件,客户端和服务端建立连接之后会维持一个 Channel。读写网络数据等等都需要Channel这个组件的参与
Channel生命同期:
| 状态 | 描述 |
|---|---|
| ChannelUnregistered | Channel已经被创建,但还未注册到EventLoop |
| ChannelRegistered | Channel已经被注册到EventLoop |
| ChannelActive | Channel已经处理活动状态并可以接收与发送数据 |
| ChannelInactive | Channel没有连接到远程节点 |
关系梳理

- 一个客户端对应一个 Channel;
- 一个 Channel 对应一个 ChannelPipeline;
- 一个 ChannelPipeline 又维护了一个双向链表,其中链表的节点是
ChannelHandlerContext; - 一个
ChannelHandlerContext则关联一个ChannelHandler(自定义 Handler); - ChannelHandler 则是自定义 Handler 的一个顶级抽象接口。
入站和出站的顺序:
- 入站: 事件会从链表的头部一直往后执行,
ChannelInboundHandlerAdapter子类会被触发; - 出站: 事件会从链表的尾部一直往前执行,
ChannelOutboundHandlerAdapter子类会被触发。
Hello World案例
1、依赖引入
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
2、服务端编写
NioEventLoopGroup是EventLoopGroup的具体实现,
NioEventLoopGroup是线程组,包含一组NIO线程,专门用于网络事件的处理
netty的实例化,需要启动两个EventLoopGroup
一个boss,一个worker
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup();初始化两个线程: 一个线程(boss)负责接受新的连接,一个(worker)负责处理读写;
bootstrap:直译为"引导"
Netty 中 ServerBootStrap 和 Bootstrap 引导器是最经典的建造者模式实现,在构建过程中需要设置非常多的参数。
//服务器端
public class HelloServer {
public static void main(String[] args) {
//创建boss线程组 用于服务端接受客户端的连接
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//创建 worker 线程组 用于进行 SocketChannel 的数据读写
EventLoopGroup workerGroup = new NioEventLoopGroup();
//创建 ServerBootstrap 对象,用于设置服务端的启动配置
ServerBootstrap b = new ServerBootstrap()
.group(bossGroup, workerGroup)
//选择服务器的ServerSocketChannel实现
.channel(NioServerSocketChannel.class)
//通道初始化
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
//添加具体的handler
channel.pipeline().addLast(new StringDecoder());
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
}
});
}
});
//服务端启动绑定端口号
ChannelFuture future = b.bind(8888);
}
}
3、ChannelFuture
获取一个ChannelFuture对象,它的作用是用来保存 Channel 异步操作的结果(咋感觉那么像promise对象??)
在 Netty 中所有的 IO 操作都是异步的,不能立刻得到 IO 操作的执行结果,但是可以通过注册一个监听器来监听其执行结果。在 Netty 当中是通过 ChannelFuture 来实现异步结果的监听
Netty 是异步操作,无法知道什么时候执行完成,在 Netty 当中 Bind 、Write 、Connect 等操作会简单的返回一个 ChannelFuture,来进行执行结果的监听。
//ChannelFuture 用来保存 Channel 异步操作的结果
ChannelFuture future = b.connect(new InetSocketAddress("localhost", 8888));
//等待异步操作执行完毕
future.sync();
------------------------------
//服务端启动绑定端口号
ChannelFuture future = b.bind(8888);
future.addListener(new GenericFutureListener<Future<? super Void>>() {
public void operationComplete(Future<? super Void> future) {
if (future.isSuccess()) {
System.out.println("端口绑定成功!");
} else {
System.err.println("端口绑定失败!");
}
}
});
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | addListener | 注册监听器,当操作已完成 (isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则通知指定的监听器 |
| 2 | removeListener | 移除监听器 |
| 3 | sync | 等待异步操作执行完毕 |
| 4 | await | 等待异步操作执行完毕 |
| 5 | isDone | 判断当前操作是否完成 |
| 6 | isSuccess | 判断已完成的当前操作是否成功 |
| 7 | isCancellable | 判断已完成的当前操作是否被取消 |
| 8 | cause | 获取已完成的当前操作失败的原因 |
sync () 和 await () 都是等待异步操作执行完成,那么它们有什么区别呢?
- sync () 会抛出异常,建议使用 sync ();
- await () 不会抛出异常,主线程无法捕捉子线程执行抛出的异常。
Future 可以通过四个核心方法来判断任务的执行情况。
| 状态 | 说明 |
|---|---|
| isDone() | 任务是否执行完成,无论成功还是失败 |
| isSuccess() | 任务是否执行采购 |
| isCancelled() | 任务是否被取消 |
| cause() | 获取执行异常信息 |
if(future.isDone()){
if(future.isSuccess()){
System.out.println("执行成功...");
}else if(future.isCancelled()){
System.out.println("任务被取消...");
}else if(future.cause()!=null){
System.out.println("执行出错:"+future.cause().getMessage());
}
}
4、客户端编写
连接的时候第一个参数为 IP 或者域名,第二个参数为端口号
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
Bootstrap b = new Bootstrap()
//EventLoop即(selector,thread)
.group(new NioEventLoopGroup())
//选择客户端实现
.channel(NioSocketChannel.class)
//添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(new StringEncoder());
}
});
//ChannelFuture 用来保存 Channel 异步操作的结果
ChannelFuture future = b.connect(new InetSocketAddress("localhost", 8888));
//等待异步操作执行完毕.sync () 会抛出异常
future.sync();
Channel channel = future.channel();
channel.writeAndFlush("hello,world");
}
}
Channe组件:Netty 的网络通信组件,客户端和服务端建立连接之后会维持一个 Channel
5、失败重连机制
在网络情况差的情况下,客户端第一次连接可能会连接失败,这个时候我们可能会尝试重新连接
private static void connect(Bootstrap bootstrap, String host, int port) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
//获取EventLoopGroup
EventLoopGroup thread=bootstrap.config().group();
//每隔5秒钟重连一次
thread.schedule(new Runnable() {
public void run() {
//递归调用连接方法
connect(bootstrap, host, port);
}
}, 5, TimeUnit.SECONDS);
}
});
}
bootstrap.config().group()
bootstrap.config().group()返回的是我们最开始配置的线程模型workerGroup,调用workerGroup的schedule方法即可实现定时任务逻辑。
通常情况下,连接建立失败不会立即重新连接,而是会通过一个指数退避的方式,根据一定是的时间间隔,比如 2 的次幂来简历连接,到达一定次数之后就放弃连接
private static void connect(Bootstrap bootstrap, String host, int port, int retry) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else if (retry == 0) {
System.err.println("重试次数已用完,放弃连接!");
} else {
// 第几次重连
int order = (MAX_RETRY - retry) + 1;
// 本次重连的间隔
int delay = 1 << order;
System.err.println(new Date() + ": 连接失败,第 " + order + " 次重连……");
bootstrap.config()
.group()
.schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS);
}
});
}
// 调用方式
// MAX_RETRY 最大重试次数
connect(bootstrap, "127.0.0.1", 8070, MAX_RETRY);
6、Hello World 测试
1.只启动客户端HelloClient,会报错
![]()
2、只启动服务端HelloServer,不会报错
3、启动服务端后,再次启动客户端

DevOps
DevOps这个词,其实就是Development和Operations两个词的组合。
在DevOps的流程下,运维人员会在项目开发期间就介入到开发过程中,了解开发人员使用的系统架构和技术路线,从而制定适当的运维方案。而开发人员也会在运维的初期参与到系统部署中,并提供系统部署的优化建议。
微服务
所谓“微服务”,就是将原来黑盒化的一个整体产品进行拆分(解耦),从一个提供多种服务的整体,拆成各自提供不同服务的多个个体。
微服务架构下,不同的工程师可以对各自负责的模块进行处理,例如开发、测试、部署、迭代。
虚拟化
虚拟化,其实就是一种敏捷的云计算服务。它从硬件上,将一个系统“划分”为多个系统,系统之间相互隔离,为微服务提供便利。
容器
在操作系统上划分为不同的“运行环境”(Container),占用资源更少,部署速度更快。
虚拟化和容器,其实为DevOps提供了很好的前提条件。开发环境和部署环境都可以更好地隔离了,减小了相互之间的影响。
云平台
公有云

安全组:防火墙
本文探讨了Netty的Reactor模式,介绍了事件驱动模型在GUI、网络服务和Web前端的应用,详细解析了单Reactor、多线程模型和主从模型,以及Netty组件如ChannelInitializer、ChannelPipeline和ChannelHandler的工作原理。同时,涉及了微服务、虚拟化和容器在DevOps中的角色以及云平台和安全组的概念。








1342

被折叠的 条评论
为什么被折叠?



