第四章 NETTY高性能架构设计

目录
一、NIO存在问题以及Netty的优点
二、线程模型基本介绍
三、工作原理图(传统同步阻塞式IO)
四、Reactor模式
五、单Reactor单线程
六、单Reactor多线程
七、主从REACTOR多线程
八、Reactor模式小结
九、Netty模型
十、异步模型

一、NIO存在问题以及Netty的优点

image-20201026190253791.png

原生NIO存在的问题
1.NIO的库和API繁杂,使用麻烦

需要掌握ServerSocketChannel,SocketChannel,Selector,ByteBuffer
需要掌握多线程,网络编程

2.传输遇到各种问题

1.断连重连,网络闪断,网络拥塞
2.半包读写
3.失败缓存
4.异常流的处理

3.JDK的NIO的Epoll Bug会导致Selector空轮询,最终导致CPU100%占用,直到JDK1.7版本这个问题依旧存在,没有根本解决

NETTY的优点
1.官网翻译

1.JBOSS 提供的一个 Java 开源框架,Netty 提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序
2.简化和流程化了 NIO 的开发过程
3.Netty获得了广泛的应用知名的 Elasticsearch 、Dubbo 框架内部都采用了 Netty。

2.优点
(1)高性能,吞吐量,延迟低,资源消耗低,不必要的内存复制(零拷贝)
(2)设计优雅,使用各种传输类型(扩展性强)的阻塞和非阻塞Socket,灵活的可扩展事件模型

定制线程模型----线程池

(3)社区活跃,版本Bug修复快,迭代周期短

3.Netty版本说明
(1)Netty5出现重大bug,已经被官网废弃了,目前推荐使用的是Netty4.x的稳定版本
(2)netty 下载地址

二、线程模型基本介绍

1.基本介绍
(1)线程模型有:传统阻塞 I/O 服务模型 Reactor 模式
(2)根据 Reactor的三种实现方式

#Reactor数量和处理的资源池中的线程数量不同,有3中实现方式
1.单 Reactor 单线程
2.单 Reactor 多线程
3.主从 Reactor 多线程(多个Reactor) 
Netty线程模式(Netty主要基于主从Reactor多线程模型做了一定的改进,其中主从 Reactor多线程模型有多个Reactor)

三、工作原理图(传统同步阻塞式IO)

image-20201027122414241.png

四、Reactor模式

image-20201027124634068.png

1.解决传统阻塞I/O模型的2个缺点

(1)多连接对应一个阻塞对象
1.1 基于I/O复用模型,多个连接共用一个阻塞对象,服务器端无需阻塞所有的连接,只需要阻塞公用的阻塞对象即可
1.2 当某个连接有数据可以处理(读事件),则OS会通知引用程序,线程从阻塞状态返回,开始业务处理

(2)复用线程池
2.1 server端不必为每个连接创建线程,连接完成后的业务处理  --分配--> 线程处理
2.2 一个线程就可以处理多个连接对象呢

(3)Reactor模式高并发关键
3.1 server端收到多个请求,分发到相应的线程,这就是网络服务器高并发的处理关键

2.I/0复用结合线程池,就是Reactor模式基本设计思想

1.复用handler和使用了线程池
2.NIO + 线程池
3.selector是单线程,而reactor是用共享的线程池处理
4.BIO没有多路复用,是简单递归,多路复用是操作系统底层实现的

3.Reactor模式中核心组成(Reactor和Handler)


image-20201027130605590.png

4.Reactor 模式分类和叫法

#分类
单 Reactor 单线程
单 Reactor 多线程
主从 Reactor 多线程

#叫法
1.反应器模式
2.分发者模式(Dispatcher)----server收到多个请求分派到相应的处理线程处理(从线程池)
3.通知者模式(notifier)---有新数据,OS通知application,线程从阻塞状态返回,进行业务处理

五、单Reactor单线程

image-20201027133929810.png

优缺点 || 场景
1.优点:模型简单,没有多线程和竞争的问题,一个线程中完成全部功能(连接,读写业务逻辑)
2.缺点:
(1)性能低: 单线程无法发挥多核 CPU 的性能。Handler在处理某个连接的业务时,整个进程则无法处理其他连接,导致应用的性能瓶颈。
(2)可靠性: 线程如果进入死循环,会导致整个系统通信模块不可用,造成节点故障
3.使用场景:Redis在业务处理的时间复杂度 O(1) 的情况

(1)客户端的数量有限,业务处理非常快速
(2)连接少,处理快,就不会阻塞单线程

六、单Reactor多线程

原理图


image-20201027181129115.png

优缺点分析
1.优点:可以充分的发挥多核CPU的性能
2.缺点:虽然处理逻辑可以由线程池来处理(并发),但是Reactor依然是单线程运行的,它需要处理所有事件的监听和响应(accept和handler都是单线程)

在高并发的场景容易出现性能瓶颈,多线程的数据共享和数据访问

七、主从REACTOR多线程

工作原理图

image-20201027184406226.png

优缺点分析
1.优点
(1)父子线程的职责清晰,MainReactor负责接收新连接,SubReactor负责后续的业务处理(read数据----多线程),在把业务处理交给线程池处理(多线程)
(2)父子线程的数据交互简单,Reactor主线程只需要把新连接传给子线程,子线程无需返回数据
2.缺点
(1)编程复杂度高
3.场景:
(1)Nginx主从Reactor多进程模型
(2)Netty主从多线程模型
(3)Memcached主从多线程模型

八、Reactor模式小结

总结

#3种模式用生活案例来理解
1.单Reactor单线程,前台接待员和服务员是同一个人(select是一个线程处理)
2.单Reactor多线程,1个前台接待员,多个服务员(线程池),接待员只负责接待
3.主从 Reactor 多线程,多个前台接待员(多个线程subReactor来handler),多个服务生(线程池)

#Reactor模式具有如下的优点
1.响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的
可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销
2.扩展性好,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源
3.复用性好,Reactor 模型本身与具体事件处理逻辑无关,具有很高的复用性

九、Netty模型

工作原理示意图1-简单版
Netty 主要基于主从 Reactors 多线程模型(如图)做了一定的改进,其中主从 Reactor 多线程模型有多个 Reactor

image-20201027192256016.png

工作原理示意图-详细版


image-20201027204823544.png

Pipeline管道的处理器Handler是关键,最后都是由它来处理读写事件
(1)管道像水流一样,中间有许多的handler处理器对水进行加工,形成矿泉水-------业务逻辑------处理数据
(2)通道更多的是传输数据,读写的操作--------read,write操作----传输数据

1.Netty快速入门实例-TCP服务

1.导入netty的包
导入到lib目录下jar包: https://www.cnblogs.com/128-cdy/p/12608779.html

<!--netty依赖-->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.20.Final</version>
</dependency>

2.编写客户端和服务器端代码

//1.NettyServer.java服务器
public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 1.创建BossGroup 和 WorkerGroup,两个都是无限循环
        // 含有多少子线程(NioEventLoop),默认是cpu核心数 * 2
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        // 2.创建服务器端的启动对象bootstrap,配置各种参数
        ServerBootstrap bootstrap = new ServerBootstrap();
        try{
            // 3.使用链式编程设置参数(底层其实是一堆的set方法)
            bootstrap.group(bossGroup,workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class)//使用NioServerSocketChannel作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG,128)//设置线程队列得到的连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //给pipeline设置处理器(管道和通道可以相互获取)
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });//给我们的worker Group的EventLoop对应的管道设置处理器
            System.out.println("...服务器 is ready...");
            // 4.绑定一个端口并同步(启动服务器),生成一个ChannelFuture对象
            ChannelFuture cf = bootstrap.bind(6668).sync();
            // 5.对关闭通道进行监听
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

//2.NettyClient.java客户端
public class NettyClient {
    public static void main(String[] args) throws Exception{
        // 1.客户端需要一个事件循环组
        NioEventLoopGroup group = new NioEventLoopGroup();
        // 2.创建客户端启动对象bootstrap,并进行相应的配置
        Bootstrap bootstrap = new Bootstrap();
        try {
            // 3.设置相关参数
            bootstrap.group(group)//设置线程组
                    .channel(NioSocketChannel.class)//设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());//加入自己的处理器
                        }
                    });
            System.out.println("客户端 ok ...");

            // 4.启动客户端连接服务器(ChannelFuture设计到Netty的异步模型)
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            // 5.监听关闭通道
            channelFuture.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }
}

3.启动效果(先启动server,在启动client)


image-20201027221621458.png

2.源码之NioEventLoop&&ChannelHandlerContext

1.NioEventLoopGroup含有多少子线程(NioEventLoop)
(1)默认是cpu核心数 * 2
(2)源码解释


(3)断点调试验证


image-20201027225616799.png

(4)修改bossGroup的子线程个数(NioEventLoop) == 1

EventLoopGroup bossGroup = new NioEventLoopGroup(1);

(5)NioEventLoop包含什么重要的信息


image-20201027232612185.png

2.查看Channel和Pipeline的关系以及Ctx(ChannelHandlerContext)

1.Channel和Pipeline的关系: 我中有你,你中有我

2.Ctx(ChannelHandlerContext)包含了丰富的信息

//NettyServerHandler.java
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务器读取线程: " + Thread.currentThread().getName());
        System.out.println("server ctx: " + ctx);
        System.out.println("看看channel和pipeline的关系");
        Channel channel = ctx.channel();
        ChannelPipeline pipeline = ctx.pipeline();//底层是一个双向链表,出栈入栈
 }
image-20201027234240654.png

3.任务队列中的Task有3种典型使用场景

1.用户自定义的普通任务 ------------> taskQueue

1.NioEventLoop(一个线程)其实就是线程池的实现(类似newSingleThreadExecutor),顶层实现了ExecutorService接口
2.任务队列"入队出队"取出任务执行,线程一个只能执行一个任务,其他放在阻塞队列,按顺序执行
image-20201028072506646.png

2.用户自定义的定时任务 ----------> scheduledTaskQueue

(1)定時任务是延迟7s执行,但是前面的任务顺序执行占用了4s,则在过3s,定时任务就会执行
(2)并不是顺序执行到定时任务的时候,对定时任务延迟7s再执行
(3)这里可以理解为定时任务和自定义任务是并行的

// 二.用户自定义的定时任务
        ctx.channel().eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello,服务器耗时长的操作完成,喵4...", CharsetUtil.UTF_8));
                    System.out.println("taskQueue中处理的线程名:" + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    System.out.println("发生异常: " + e.getMessage());
                }
            }
        },7, TimeUnit.SECONDS);// 延迟5s执行这个任务(前面的任务顺序执行占用了4s,则在过3s,定时任务就会执行)
        System.out.println("go on...");
image-20201028081816943.png

3.系统向多用户推送消息
1.非当前Reactor线程调用Channel的方法
2.推送系统可以根据用户表示找到对应的Channel引用,然后调用通道的eventLoop的execute方法提交到任务队列执行

1.可以用一个集合管理SocketChannel
2.推送消息,可以将业务加入到各个channel对应的NioEventLoop的taskQueue或者scheduledTaskQueue
ctx.channel().eventLoop().execute(new Runnable());
ctx.channel().eventLoop().schedule(Runnable);

4.总结

1.Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作
2.Netty模型基础知识
(1)NioEventLoopGroup 下包含多个 NioEventLoop,每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
(2)NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 selector,用于监听绑定在其上的 socket 网络通道(监听多个 NioChannel)每个 NioChannel 只会绑定在唯一的 NioEventLoop 上

2.1 NioEventLoop 内部采用串行化设计,从消息的读取->解码-><u>处理</u>->编码->发送,始终由 IO 线程 NioEventLoop 负责
2.2 处理的操作耗时较长,则会交给任务队列,异步执行

(3)每个 NioChannel 都绑定有一个自己的 ChannelPipeline(互相包含关系)

十、异步模型

1.基本介绍

(1)异步调用之后,调用者不能马上得到结果,实际上是在处理调用方法之后,通过通知来回调方法
(2)Netty 中的 I/O 操作是都是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。

ChannelFuture cf = bootstrap.bind(6668).sync();

(3)Future-Listener机制,用户可以方便的获取IO操作结果
(4)Netty异步模型建立在future和callback之上的

callback容易理解,就是回调
future:举个栗子,调用一个非常耗时的方法fun,等待结果显示不合适,这时先返回一个Future对象,后续用Future对象监控fun方法的执行状态(Future-Listener机制),获取状态作出相应的处理(回调方法去处理)

2.深入理解Future-Listener机制

#1.ChannelFuture源码
The result of an asynchronous {@link Channel} I/O operation.
解释:是一个异步IO操作的结果

#2.ChannelFuture和JDK1.8新加个FutureTask都是继承与JUC的Future接口,异步回调
(1)ChannelFuture 是一个接口 : public interface ChannelFuture extends Future<Void>
(2)future可以添加监听器,当监听的事件发生时,就会通知到监听器.

#3.前端的Ajax也是异步回调,加上Future都是观察者模式(监听--通知---回调)

3.链式操作 + handler的异步操作示意图

image-20201028113418370.png

4.ChannelFuture来获取操作执行的状态

image-20201028161156395.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容