【知识梳理】Netty知识梳理

Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。拥有比Java核心API更高的吞吐量以及更低的延迟。

Netty高效的原因:零拷贝(引用)、责任链(高效扩展)、内存池

阻塞IO缺点:

(1)大量线程处于休眠状态

(2)需要为每个线程的调用栈分配内存

(3)上下文切换所带来的开销会非常麻烦

非阻塞IO (NIO)

class java.nio.channels.Selector 是Java的非阻塞I/O实现的关键,它使用了事件通知API以确定在一组非阻塞套接字中有哪些已经就绪能够进行I/O相关的操作。

单一的线程便可以处理多个并发的连接

特点:

(1)使用较少的线程可以处理许多连接,因此也减少了内存管理和上下文切换所带来的开销

(2)当没有I/O操作需要处理的时候,线程也可以被用于其他任务


编写Echo服务器

  • 至少一个ChannelHandler
  • 引导

每个Channel都拥有一个与之相关联的ChannelPipeline,其持有一个ChannelHandler的实例链,在默认情况下,ChannelHandler会把对它的方法的调用转发给链中的下一个ChannelHandler

服务器实现步骤:

  • 创建一个ServerBootstrap的实例以引导和绑定服务器
  • 创建并分配一个NioEventLoopGroup实例以进行事件的处理,如接收新连接以及读写数据
  • 指定服务器绑定的本地的InetSocketAddress
  • 使用一个EchoServerHandler的实例初始化每一个新的Channel
  • 调用ServerBootstrap.bind()方法以绑定服务器

编写Echo客户端

实现步骤:

  • 为初始化客户端,创建了一个Bootstrap实例
  • 为进行事件处理分配了一个NioEventLoopGroup实例,其中事件处理包括创建新的连接以及处理入站和出站数据
  • 为服务器连接创建一个InetSocketAddress
  • 当连接被创建时,一个EchoClientHnadler实例会被安装到ChannelPipeline中
  • 在一切都设置完成后,调用Bootstrap.connect()方法连接到远程节点

运行:

mvn exec:java -Dexec.mainClass=“EchoServer” -Dexec.args=“8055”
mvn exec:java -Dexec.mainClass=“EchoClient” -Dexec.args=“localhost 8055”


Netty核心组件

组件功能
ChannelSocket
EventLoop控制流,多线程处理,并发
ChannelFuture异步通知

Channel :数据载体

代表一个实体的开放链接,如读操作写操作。可以看做传入传出数据的载体。(可以被打开或者关闭,连接或者断开)。Netty的Channel接口所提供的API,大大降低了Socket类的复杂性。

Channel拥有许多预定义的,专门实现的广泛类层次结构的根:

  • EmbeddedChannel
  • LocalServerChannel
  • NioDatagramChannel
  • NioSctpChannel
  • NioSocketChannel
  • Channel是线程安全的

Channel的状态:

ChannelRegistered (Channel被注册到EventLoop)

–> ChannelActive (Channel处于活动状态)

–> ChannelInactive (Channel没有连接到远程节点)

–> ChannelUnregistered (Channel 已经被创建,未被注册到EventLoop)


ChannelFuture:在addListener方法中注册了一个ChannelFutureListener,以便在某个操作完成时得到通知


EventLoop:处理事件,执行任务

EventLoop定义了Netty的核心抽象,用户处理生命周期中所发生的事件。

采用了两个基本的API:并发网络编程

一个EventLoopGroup包含一个或多个EventLoop,一个EventLoop在它的生命周期内只和一个Thread绑定
所有由EventLoop处理的I/O事件都将在它专有的Thread上被处理

一个Channel在它的生命周期内只注册一个EventLoop,一个EventLoop可能会被分配给一个或多个Channel

使用EventLoop,创建任务稍后执行

ScheduledFuture<?> future = ch.eventLoop().schedule(
new Runnale(){
	@Override
	public void run(){
	...
	}
},60,TimeUnit.SECONDS
);

使用EventLoop,周期性的任务

ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(
new Runnale(){
	@Override
	public void run(){
    ...
	}
},60,60,TimeUnit.SECONDS
);

EventLoopGroup


ChannelHandler:数据的逻辑容器

入站和出站数据的应用程序逻辑的容器(应用程序的业务逻辑)
Netty以适配类的形式提供了大量默认ChannelHandler实现,简化逻辑开发过程

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter
  • ChannelDuplexHandler

ChannelHnadler的典型应用包括:

  • 将数据从一种格式转换为另一种格式
  • 提供异常的通知
  • 提供Channel变为活动或非活动的通知
  • 提供Channel注册到EventLoop或者从EventLoop注销时的通知
  • 提供有关用户自定义事件的通知
  • ChannelInboundHandler 接口
  • ChannelOutboundHandler 接口

参考:https://blog.csdn.net/MOVIE14/article/details/75077817

ChannelHandlerContext:
ChannelHandlerContext和ChannelHandler之间是关联的
ChannelHandlerContext使得ChannelHandler能够和它的ChannelPipeline以及其他的ChannelHandler配合工作。

每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext。

ChannelHandler可以用@Sharable标注,可以从属多个ChannelPipline
(在多个ChannelPipline中安装多个ChannelHandler用于收集多个Channel的统计性信息)

异常:ChannelHandler.exceptionCaught() 默认将异常转发给ChannelPipline中的下一个ChannelHnadler,如果达到ChannelPipline的尾端,将会被记录为未被处理。

ChannelPipeline

ChannelPipeline提供了ChannelHandler链的容器,并定义了用于在该链上传播入站和出站时间流的API。ChannelHandler的执行顺序与被添加的顺序一致。

当ChannelHnadler被添加到ChannelPipeline时,它将会被分配一个ChannelHandlerContext,代表了ChannelHandler和ChannelPipeline之间的绑定。

ChannelPipeline实现了常用的设计模式:拦截器模式

ChannelPipeline会查看下一个ChannelHandler是否匹配,如果不匹配会跳过进入下一个
ChannelPipline可以通过remove,replace修改handler布局

netty中我们会添加一些处理器,如果对其添加流程及内部结构不清楚经常会出现各种问题,我先给出一幅处理器整体排版图
在这里插入图片描述
addFist添加Handler是将Handler放在Head后面

addLast添加Handler是将Handler放在Tail的前面

消息接收时是从Head开始向Tail流动直到某个handler没有将事件传递下去,或者tail结束(事件未传递是对应handler未调用ctx.fireChannelRead())

消息发送一般是从Tail开启到head结束。当用户调用ctx.pipeline().writeAndFlush(xxx)输出流是从tail开启。如果我们在某个handler执行ctx.writeAndFlush(xx)那么事件是从当前Handler向Head流出。如果存在某个Handler未调用ctx.write()则事件会到此结束无法向head流动

参考:https://blog.csdn.net/yinbucheng/article/details/90710293


引导

引导涉及到:

(1)将一个进程绑定到某一个指定的端口

(2)或将一个进程连接到另一个运行在某个指定主机的指定端口上的进程

服务端:ServerBootstrap将绑定到一个端口,因为服务器必须要监听连接
客户端:Bootstrap则是由想要连接到远程节点的客户端应用程序所使用的

在引导中只能添加一个Handler,如果需要将多个Hnadler注册到一个ChannelPipline中,需要提供ChannelInitializer实现(initChannel方法)

Channel属性的批量配置:ChannelOption
bootstrap.option(ChannelOption.SO_KEEPALIVE,true)
option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000);

基于UDP:DatagramChannel


回调:已经被提供给另一个方法的方法的引用(处理触发的事件)


Future:提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;在未来某个时刻完成,并提供给其结果的访问。

Netty提供了ChannelFuture,用户在执行异步操作的时候使用(回调的更精细版本)

Channel channel = ...;
ChannelFuture future = channel.connect(new InetSocketAddress("..."));
future.addListener(new ChannelFutureListener(){
@Override
public void operationComplete(Channel future){
	if(future.isSuccess()){
		ByteBuf buffer = Unpooled.copiedBuffer("Hello",Charset.defaultCharset());	
		ChannelFuture wf = future.channel().writeAndFlush(buffer);
	 ... ...
	}else {
	Throwable cause = future.cause();
	cause.printStackTrace();
	 }  
 }
}
});

Netty编程基本逻辑(以Server为例)

关系梳理:

引导—>EventLoopGroup—>EventLoop—>各个Channel(事件的载体)
—>Channel内部有Pipeline—>Pipeline中有各个Handler—>使用ByteBuf

public void start() throws Exception {
	final EchoServerHandler serverHandler = new EchoServerHandler();//Hnadler处理逻辑
	EventLoopGroup group = new NioEventLoopGroup();//EventLoop组
	try {
		ServerBootstrap b = new ServerBootstrap();//引导
	 	//组装引导、EventLoopGroup、channel类型、地址、ChannelHandler调用链
		b.group(group).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
		.childHandler(new ChannelInitializer<SocketChannel>() {

		@Override
		public void initChannel(SocketChannel ch) throws Exception {
			ch.pipeline().addLast(serverHandler);//pipeline: ChannelHandler链
		}
	 });
		//引导执行
		ChannelFuture f = b.bind().sync();
		//结束
		f.channel().closeFuture().sync();
	} finally {
 		//关闭EventLoop组
		group.shutdownGracefully().sync();
 	}
}

Netty的其他版本:

  • 不通过Netty的OIO
  • 不通过Netty的NIO
  • 基于选择器(注册表),可以请求在Channel的状态发生变化时得到通知。
  • 通过Netty的OIO
  • 通过Netty的NIO

https://github.com/vicotorz/NettyDemo/tree/master/src/main/java/Demo

Nioio.netty.channel.socket.nioio.netty.channel.epoll 非阻塞代码库或者常规起点
Oioio.netty.channel.socket.oio 阻塞代码库
Localio.netty.channel.local 在同一个JVM内部通信
Embeddedio.netty.channel.embedded 测试ChannelHandler实现单元测试

ByteBuf (Netty的数据容器)

Java Nio提供了ByteBuffer作为字节容器,但使用起来过于复杂。
Netty提供了ByteBuf,解决了ByteBuffer的局限性。

  • abstract class ByteBuf
  • interface ByteBufHolder

ByteBuf优点:

  • 它可以被用户自定义的缓冲区类型扩展
  • 通过内置的复合缓冲区类型实现了透明的零拷贝
  • 容量可以按需增长
  • 在读和写两种模式之间切换不需要调用ByteBuffer的flip()方法
  • 读和写使用了不同的索引(读索引,写索引)(JDK的ByteBuffer只有一个索引,因此需要调用flip())
  • 支持方法的链式调用
  • 支持引用计数
  • 支持池化

1.堆缓存区

支撑数组模式,在没有使用池化的情况快速的分配和释放,JVM堆中

ByteBuf heapBuf = ...;
if(heapBuf.hasArray()){
	int length = heapBuf.readableBytes();
	int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();
	byte[] array = heapBuf.array();
	handleArray(array,offset,length);
}

2.直接缓冲区
使用直接缓冲区,分配和释放代价比较高,适用于网络传输

ByteBuf directBuf = ...;
if(!directBuf.hasArray()){
	int length = directBuf.readableBytes();
	byte[] array = new byte[length];
	directBuf.getBytes(directBuf.readerIndex(),array);
	handleArray(array,0,length);
}

3.复合缓冲区

CompositeByteBuf 将多个缓冲区 表示为单个合并缓冲区的虚拟表示(为多个ByteBuf提供一个聚合视图)
例如:HTTP协议传输(头和主体),可以使用CompositeByteBuf的方式进行封装。

ByteBuffer[] message = new ByteBuffer[]{header,body}
ByteBuffer message2 = ByteBuffer.allocate(header.remaining() + body.remaining());
message2.put(header);
message2.put(body);
message2.flip();

CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
ByteBuf headerBuf = ...
ByteBuf bodyBuf = ...
messageBuf.addComponents(headerBuf,bodyBuf);

messageBuf.removeComponent(0);//删除位于索引位置为0的ByteBuf


//读取数据
int length = compBuf.readableBytes();
byte[] array = new byte[length];
compBuf.getBytes(compBuf.readerIndex(),array);//将字节读入到数组中
handleArray(array,0,array.length);

可丢弃字节(discardReadBytes()),可读字节,可写字节

图(p59)

  • markReaderIndex()

  • markWriterIndex()

  • resetReaderIndex()

  • resetWriterIndex()

  • readerIndex(int)

  • writerIndex(int)

  • clear() 比 discardReadBytes()轻量得多,只是重置索引而不会复制任何内存

  • process(byte value)

  • forEachByte(ByteBufProcessor.FIND_NUL)

派生缓冲区:

  • duplicate()
  • slice()
  • slice(int,int)
  • Unpooled.unmodifiableBuffer(…)
  • order(ByteOrder)
  • readSlice(int)

ByteBufHolder ?

ByteBufAllocator
实现了ByteBuf池化,可以分配任意类型的ByteBuf实例,通过引用计数的方式降低内存分配开销

  1. PooledByteBufAllocator:最大限度减少内存碎片
  2. UnpooledByteBufAllocator:每次调用都会返回一个新的实例

ByteBufUtil

用于操作ByteBuf的静态的辅助方法

例如:hexdump(),以十六进制的形式打印ByteBuf的内容

ByteBuf 复制:

如果需要一个现有缓冲区的真实副本,使用copy() 或者 copy(int,int)

Netty提供了class ResourceLeakDetector对应用程序缓冲区做大约1%的采样检测内存泄漏


编码、解码器

将字节解码为消息:

ByteToMessageDecoder、ReplayingDecoder、MessageToByteEncoder、MessageToMessageEncoder

将一种消息类型解码为另一种:

MessageToMessageDecoder
CombinedChannelDuplexHandler

SSLHandler

管理连接:

  • idleStateHandler 连接空闲时间时长
  • ReadTimeoutHandler 如果在指定时间没有收到入站数据,则抛出一个Read-TimeoutException异常
  • WriteTimeoutHandler 如果在指定时间没有收到出站数据,则抛出一个WriteTimeoutException异常
public class IdleStateHandlerInitializer extends ChannelInitializer<Channel>{
	protected void initChannel(Channel ch) throws Exception{
    	ChannelPipeline pipeline = ch.pipeline();
    	pipeline.addLast(new IdleStateHandler(0,0,60,TimeUnit.SECONDS));
    	pipeline.addLast(new HeartbeatHandler());
	}

	public static final class HeartbeatHandler extends ChannelInboundHandlerAdapter{
    	private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleaseableBuffer(
        Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HEARTBEAT",CharsetUtil.IOS_8859_1));
    )
}

public void userEventTriggered(ChannelHandlerContext ctx,Object evt) throws Exception{
    if(evt instanceof IdleStateEvent){
    	ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);         
    }else{
        super.userEventTriggered(ctx,evt);
    }
}}
  • 基于分隔符协议:LineBasedFrameDecoder
  • 基于长度的协议:FixedLengthFrameDecoder
  • 写大型数据(基于FileRegion)

netty粘包与拆包

UDP不存在粘包与拆包,UDP是基于报文发送的,UDP首部采用了16bit来指示UDP数据报文的长度。可以将不同的数据报文区分开,避免粘包和拆包的问题

TCP是基于字节流的,应用层和TCP传输层之间的数据交互是大小不等的数据块,TCP把这些数据块看成一连串无结构的字节流,没有边界,且首部没有表示数据长度的字段,可能发生粘包。

产生原因

要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

来自 https://blog.battcn.com/2017/09/01/netty/netty-4/

解决:添加边界信息、或将没个数据包封装固定长度、在数据包之间添加特殊符号

netty提供的decoder:

DelimiterBasedFrameDecoder 基于消息边界方式进行粘包拆包处理的。 FixedLengthFrameDecoder 基于固定长度消息进行粘包拆包处理的。 LengthFieldBasedFrameDecoder 基于消息头指定消息长度进行粘包拆包处理的。 LineBasedFrameDecoder 基于行来进行消息粘包拆包处理的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"Java高级架构面试知识点整理.pdf"是一份关于Java高级架构的面试知识点的文档。该文档主要包括以下几个方面的内容: 1. Java多线程和并发:讲解Java中的多线程概念、线程安全性、线程的生命周期和状态转换、线程同步与锁、并发工具类(如CountDownLatch、Semaphore等)、线程池等相关知识点。 2. JVM与GC算法:了解Java虚拟机(JVM)的基本原理、内存结构和内存模型,理解垃圾回收(GC)算法的原理和常见的垃圾回收器(如Serial、Parallel、CMS、G1等),掌握GC调优的一般方法。 3. 分布式架构和并发编程模型:认识分布式系统的基本概念、CAP定理、分布式存储和分布式计算的方案,了解常见的并发编程模型(如Actor模型、异步编程等)和实现方式。 4. 高性能网络编程:熟悉Java NIO的基本原理、底层实现和使用方式,了解Java网络编程的相关API和概念(如Socket、ServerSocket、Selector等),了解基于Netty框架的高性能网络编程。 5. 分布式消息队列和中间件:了解消息队列的基本概念、常见的消息中间件(如RabbitMQ、Kafka等)的特点和使用场景,理解消息队列的高可用、持久化、消息顺序等特性。 6. 微服务和服务治理:理解微服务的概念、优劣势和架构特点,了解微服务的拆分和组织原则,熟悉常见的服务治理框架(如Spring Cloud、Dubbo等)和相关的技术组件。 7. 高可用和容灾设计:掌握高可用架构的设计原则和常见的高可用技术方案(如集群、负载均衡、故障切换等),了解容灾方案的设计和实现方法,学习如何保证系统的可靠性和稳定性。 8. 性能优化与调优:了解性能优化的基本思路和方法,熟悉性能调优的一般流程和工具,掌握常见的性能调优技术(如缓存、异步、批处理等)和优化手段。 以上就是对于"Java高级架构面试知识点整理.pdf"文档的简要回答,希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值