随着移动互联网的爆发性增长,小明公司的电子商务系统访问量越来越大,由于现有系统是个单体的巨型应用,已经无法满足海量的并发请求,拆分势在必行。
在微服务的大潮之中, 架构师通常会把系统拆分成了多个服务,根据需要部署在多个机器上,这些服务非常灵活,可以随着访问量弹性扩展。
世界上没有免费的午餐, 拆分成多个“微服务”以后虽然增加了弹性,但也带来了一个巨大的挑战:各个服务之间互相调用的开销。
比如说:原来用户下一个订单需要登录,浏览产品详情,加入购物车,支付,扣库存等一系列操作,在单体应用的时候它们都在一台机器的同一个进程中,说白了就是模块之间的函数调用,效率超级高。
现在好了,服务被安置到了不同的服务器上,一个订单流程,几乎每个操作都要越网络,都是远程过程调用(RPC), 那执行时间、执行效率可远远比不上以前了。
远程过程调用的第一版实现使用了HTTP协议,也就是说各个服务对外提供HTTP接口。HTTP协议虽然简单明了,但是太过繁琐太多,仅仅是给服务器发个简单的消息都会附带一大堆无用信息:
GET /orders/1 HTTP/1.1
Host: order.myshop.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; )
Accept: text/html;
Accept-Language: en-US,en;
Accept-Encoding: gzip
Connection: keep-alive
看看那User-Agent,Accept-Language ,这个协议明显是为浏览器而生的!对于各个应用程序之间的调用,用HTTP协议得不偿失。
能不能自定义一个精简的协议? 在这个协议中我们只需要把要调用方法名和参数发给服务器即可,根本不用这么多乱七八糟的额外信息。
但是自定义协议客户端和服务器端就得直接使用“低级”的Socket了,尤其是服务器端,得能够处理高并发的访问请求才行。
阻塞IO与非阻塞IO
至于服务器端的socket编程,最早的Java是所谓的阻塞IO(Blocking IO), 想处理多个socket的连接的话需要创建多个线程, 一个线程对应一个。
这种方式写起来倒是挺简单的,但是连接(socket)多了就受不了了,如果真的有成千上万个线程同时处理成千上万个socket,占用大量的空间不说,光是线程之间的切换就是一个巨大的开销。
更重要的是,虽然有大量的socket,但是真正需要处理的(可以读写数据的socket)却不多,大量的线程处于等待数据状态(这也是为什么叫做阻塞的原因),资源浪费得让人心疼。
后来Java为了解决这个问题,又搞了一个非阻塞IO(NIO:Non-Blocking IO,有人也叫做New IO), 改变了一下思路:通过多路复用的方式让一个线程去处理多个Socket。
这样一来,只需要使用少量的线程就可以搞定多个socket了,线程只需要通过Selector去查一下它所管理的socket集合,哪个Socket的数据准备好了,就去处理哪个Socket,一点儿都不浪费。
这样一来,只需要使用少量的线程就可以搞定多个socket了,线程只需要通过Selector去查一下它所管理的socket集合,哪个Socket的数据准备好了,就去处理哪个Socket,一点儿都不浪费。
Java NIO 由三个核心组件组件:
Buffer
Channel
Selector
缓冲区和通道是NIO中的核心对象,通道Channel是对原IO中流的模拟,所有数据都要通过通道进行传输;Buffer实质上是一个容器对象,发送给通道的所有对象都必须首先放到一个缓冲区中。
Buffer是一个数据对象,它包含要写入或者刚读出的数据。这是NIO与IO的一个重要区别,我们可以把它理解为固定数量的数据的容器,它包含一些要写入或者读出的数据。在面向流的I/O中你将数据直接写入或者将数据直接读到stream中,在Java NIO中,
任何时候访问NIO中的数据,都需要通过缓冲区(Buffer)进行操作。读取数据时,直接从缓冲区中读取,写入数据时,写入至缓冲区
。缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组。
缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。简单的说Buffer是:一块连续的内存块,是NIO数据读或写的中转地
。NIO最常用的缓冲区则是ByteBuffer。下图是 Buffer 继承关系图:
从类图可以看出NIO为所有的原始数据类型都实现了Buffer缓存的支持。并且看JDK_API可以得知除了ByteBuffer中的方法有所不同之外,其它类中的方法基本相同。
Channel 是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。正如前面提到的,所有数据都通过 Buffer 对象来处理。你永远不会将字节直接写入通道中,相反,你是将数据写入包含一个或者多个字节的缓冲区。同样,你不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。简单的说Channel是:
数据的源头或者数据的目的地,用于向buffer提供数据或者读取buffer数据,并且对I/O提供异步支持
。
下图是 Channel 的类图
Channel 为最顶层接口,所有子 Channel 都实现了该接口,它主要用于 I/O 操作的连接。定义如下:
public interface Channel extends Closeable {
* 判断此通道是否处于打开状态。
public boolean isOpen();
*关闭此通道。
public void close() throws IOException;
最为重要的Channel实现类为:
FileChannel:一个用来写、读、映射和操作文件的通道
DatagramChannel:能通过UDP读写网络中的数据
SocketChannel: 能通过TCP读写网络中的数据
ServerSocketChannel:可以监听新进来的 TCP 连接,像 Web 服务器那样。对每一个新进来的连接都会创建一个SocketChannel
使用以下三个方法可以得到一个FileChannel的实例
FileInputStream.getChannel()
FileOutputStream.getChannel()
RandomAccessFile.getChannel()
上面提到Channel是数据的源头或者数据的目的地,用于向bufer提供数据或者从buffer读取数据。那么在实现了该接口的子类中应该有相应的read和write方法。
在FileChannel中有以下方法可以使用:
* 读取一串数据到缓冲区
public long read(ByteBuffer[] dsts)
* 将缓冲区中指定位置的一串数据写入到通道
public long write(ByteBuffer[] srcs)
多路复用器 Selector,它是 Java NIO 编程的基础,它提供了选择已经就绪的任务的能力。从底层来看,
Selector 提供了询问通道是否已经准备好执行每个 I/O 操作的能力。简单来讲,Selector 会不断地轮询注册在其上的 Channel,如果某个 Channel 上面发生了读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作
。
Selector 就是你注册对各种 I/O 事件的地方,而且当那些事件发生时,就是这个对象告诉你所发生的事件。Selector 允许一个线程处理多个 Channel ,也就是说只要一个线程复杂 Selector 的轮询,就可以处理成千上万个 Channel ,相比于多线程来处理势必会减少线程的上下文切换问题
。
* 第一步:创建一个Selector
Selector selector = Selector.open();
* 第二步:打开一个远程连接
InetSocketAddress socketAddress =
new
InetSocketAddress(
"www.baidu.com"
,
80
);
SocketChannel sc = SocketChannel.open(socketAddress);
sc.configureBlocking(
false
);
* 第三步:选择键,注册
SelectionKey key = sc.register(selector, SelectionKey.OP_CONNECT);
* 注册时第一个参数总是当前的这个selector。
* 注册读事件
* 注册写事件
SelectionKey key = sc.register(selector, SelectionKey.OP_READ);
SelectionKey key = sc.register(selector, SelectionKey.OP_WRITE);
* 第四步:内部循环处理
int
num = selector.select();
Set selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while
(iterator.hasNext()) {
SelectionKey key = (SelectionKey)it.next();
SelectionKey selectionKey = iterator.next();
iterator.remove();
首先,我们
调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。该方法必须首先执行
。
接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个集合。SelectionKey中共定义了四种事件,OP_ACCEPT(socket accept)、OP_CONNECT(socket connect)、OP_READ(read)、OP_WRITE(write)。我们
通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象
。
在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。
如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey
。
基于NIO的高并发RPC框架
开发一个具有较好的稳定性和可靠性的 NIO 程序还是挺有难度的。于是 Netty 出现,把我们从水深火热当中解救出来。说说Netty到底是何方神圣, 要解决什么问题吧。回到前言中提到的例子,如果使用Java NIO来自定义一个高性能的RPC框架,调用协议,数据的格式和次序都是自己定义的,现有的HTTP根本玩不转,那使用Netty就是绝佳的选择。
使用Netty的开源框架,可以快速地开发高性能的面向协议的服务器和客户端。 易用、健壮、安全、高效,你可以在Netty上轻松实现各种自定义的协议!其实游戏领域是个更好的例子,长连接,自定义协议,高并发,Netty就是绝配。
因为Netty本身就是一个基于NIO的网络框架, 封装了Java NIO那些复杂的底层细节,给你提供简单好用的抽象概念来编程。
注意几个关键词,首先
它是个框架,是个“半成品”,不能开箱即用,你必须得拿过来做点定制,利用它开发出自己的应用程序,然后才能运行(就像使用Spring那样)
。一个更加知名的例子就是阿里巴巴的Dubbo了,这个RPC框架的底层用的就是Netty。 另外一个关键词是高性能,如果你的应用根本没有高并发的压力,那就不一定要用Netty了。
参考资料:https://blog.csdn.net/bjweimengshu/article/details/78786315
资源名称:
Java
-
NIO
-
Netty
框架
学习资源目录:【】
Netty
5.0架构剖析和源码解读【】
Netty
5用户指南【】
Netty
_in_Action(第五版-目录修正版)【】
Netty
_in_Action_v08_MEAP【】
Netty
_in_Action_v10_MEAP【】
Netty
_代码分析【】
Netty
系列之Nett
资源太大,传百度网盘了,链接在附件
中
,有需要的同学自取。
1. 背景
1.1. 惊人的性能数据
最近一个圈内朋友通过私信告诉我,通过使用
Netty
4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨
节点远程服务调用。相比于传统基于
Java
序列化+BIO(同步阻塞IO)的通信
框架
,性能提升了8倍多。
事实上,我对这个数据并不感到惊讶,根据我5年多的
NIO
编程经验,通过选择合适的
NIO
框架
,加上高性能的压缩二进制编解码技术,精
心的设计Reactor线程模型,达到上述性能指标是完全有可能的。
下面我们就一起来看下
Netty
是如何支持10W TPS的跨节点远程服务调用的,在正式开始讲解之前,我们先简单介绍下
Netty
。
Channel 与 Buffer。。若需要使用
NIO
系统,需要获取用于连接 IO 设备的通道 以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理简而言之,其
中
FileChannel 主要用于文件传输,其余三种用于网络通信,其
中
使用较多的是 ByteBuffer。
文章目录简介
NIO
常用用法
NIO
和EventLoopGroup
Nio
EventLoopGroupSelectorProviderSelectStrategyFactoryRejectedExecutionHandlerEventLoopTaskQueueFactory
Nio
EventLoop总结
netty
为什么快呢?这是因为
netty
底层使用了
JAVA
的
NIO
技术,并在其基础上进行了性能的优化,虽然
netty
不是单纯的
JAVA
nio
,但是
netty
的底层还是基于的是
nio
技术。
nio
是JDK1.
BIO,
NIO
,
Netty
简介,BIO,AIO,
NIO
的区别,
NIO
的Buffer缓冲区,通道,以及处理
java
.
nio
.BufferOverflowException异常!欢迎指出错误!
NIO
全称Non Blocking IO,同步非阻塞IO
框架
。服务器实现模式为一个线程可以处理多个请求(连接),客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理
为什么用
NIO
常见的网络服务
中
,如果每一个客户端都维持一个与登陆服务器的连接。那么服务器将维护多个和客户端的连接以出来和客户端的contnect 、read、write ,特别是对于长链接的服务,有多少个c端,就需要在s端维护同等的IO连接。这对服务器来说
Netty
是由 JBOSS 提供的一个
Java
开源
框架
,现为 Github上的独立项目。
Netty
是一个异步的、基于事件驱动的网络应用
框架
,用以快速开发高性能、高可 靠性的网络 IO 程序。
Netty
主要针对在TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer场景下 的大量数据持续传输的应用。
Netty
本质是一个
NIO
框架
,适用于服务器通讯相关的多种应用场景
要透彻理解
Netty
, 需要先学习
NIO
, 这样我们才能阅读
Netty
的源码。
1.
java
NIO
这里强调一点,
netty
仅是在
NIO
的基础上进行了优化,简化了API 的使用,解决了一些bug ,并没有从0到1的开发
NIO
; so , 一些
NIO
的核心特性,我们还是要学习原生的;
java
NIO
有几个核心的对象需要掌握: 缓冲区(Buffer)、选择器(Selector)、通道(channel)
1.1 Buffer
在
NIO
库
中
,所有数据都是用缓冲区处理的。
在读数据时,它是直接读到缓冲区;
在写数据时,它也是写入到缓冲区
中
;
在面向流I/O 系统
中
,所
BIO同步并阻塞(传统阻塞型):一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。
NIO
同步非阻塞:一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。
AIO异步非阻塞:一个有效请求一个线程,客户端的 I/O 请求都是由 OS 先完成了再通知服务器应用去
启动线程进行处理。
BIO与
NIO
的区别
BIO 是面向流的,
NIO
是面向缓冲区的或者
nio
、
netty
以及 Dubbo 入门0
Netty
简介1 BootStrap1.1 启动器初步介绍1.2 BootStrap 执行流程2
Netty
入门2.1 服务端2.1.1
Netty
Server 以及相关类2.2 客户端2.2.1
Netty
Client 以及相关类2.3 通信协议2.3.1 codec 通信消息体2.4 消息分发2.5
Netty
ServerConfig 和
Netty
ClientConfig3
Netty
解决粘包和拆包问题3.1 粘包和拆包3.2 常见解决方案3.3 N
细心的读者可能观察到,我在标题
中
关于"
NIO
"的词汇,加了双引号,在小学的时候啊,语文老师经常跟我们说,这里加双引号是什么什么作用,那我这边加双引号是要表示什么意思呢?哈哈哈,原谅我卖关子了,马上为大家带来我的看法,在
netty
中
,很多资料其实都是说这是一个高性能的
NIO
框架
(事实上也没错),我看了
netty
的源码,发现里面还包含了关于 OIO 的封装,但是 OIO 在新版本
中
的方法...
一、
Java
NIO
【
Netty
系列】
最近在B站(ps:小破站真不错!!!)学习
Netty
相关的视频,有了输入当然就要输出啦,不然脑子就堵死了,开个玩笑,要养成记笔记的习惯(ps:因为大佬都是这么说的),所以在这里做一个笔记的记录,有所感,有所悟。想去看视频的,B站链接在文章末供上。
ok,言归正传,在学习
Netty
之前,我们先了解一下
NIO
,因为
Netty
本质是一个
NIO
框架
。