当前位置: 首页 > news >正文

外贸商城网站建设公司支持微信支付的网站开发

外贸商城网站建设公司,支持微信支付的网站开发,wordpress 访问统计插件,wordpress 社交图标目录 一、概述 1、什么是Netty 2、Netty的优势 二、入门案例 1、服务器端代码 2、客户端代码 3、运行流程 组件解释 三、组件 1、EventLoop 处理普通与定时任务 关闭 EventLoopGroup 处理IO任务 服务器代码 客户端代码 分工细化 划分Boss 和Work 增加自定义EventLoopGroup 切换… 目录 一、概述 1、什么是Netty 2、Netty的优势 二、入门案例 1、服务器端代码 2、客户端代码 3、运行流程 组件解释 三、组件 1、EventLoop 处理普通与定时任务 关闭 EventLoopGroup 处理IO任务 服务器代码 客户端代码 分工细化 划分Boss 和Work  增加自定义EventLoopGroup 切换的实现 2、Channel ChannelFuture 连接问题 处理关闭  为什么Netty要将多个API调用NIO线程来异步实现 3、Future与Promise 概念 JDK Future Netty Future Netty Promise 4、Handler与Pipeline Pipeline OutboundHandler socketChannel.writeAndFlush() ctx.writeAndFlush() EmbeddedChannel 5、ByteBuf 创建 直接内存与堆内存 池化与非池化 组成 写入 扩容 读取 释放 释放规则 切片 一、概述 1、什么是Netty Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers clients. Netty 是一个异步的、基于事件驱动的网络应用框架用于快速开发可维护、高性能的网络服务器和客户端 注意netty的异步还是基于多路复用的并没有实现真正意义上的异步IO 2、Netty的优势 如果使用传统NIO其工作量大bug 多 需要自己构建协议解决 TCP 传输问题如粘包、半包因为bug的存在epoll 空轮询导致 CPU 100% Netty 对 API 进行增强使之更易用如 FastThreadLocal ThreadLocalByteBuf ByteBuffer 二、入门案例 1、服务器端代码 public class HelloServer {public static void main(String[] args) {// 1、启动器负责装配netty组件启动服务器new ServerBootstrap()// 2、创建 NioEventLoopGroup可以简单理解为 线程池 Selector.group(new NioEventLoopGroup())// 3、选择服务器的 ServerSocketChannel 实现.channel(NioServerSocketChannel.class)// 4、child 负责处理读写该方法决定了 child 执行哪些操作// ChannelInitializer 处理器仅执行一次// 它的作用是待客户端SocketChannel建立连接后执行initChannel以便添加更多的处理器.childHandler(new ChannelInitializerNioSocketChannel() {Overrideprotected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {// 5、SocketChannel的处理器使用StringDecoder解码ByteBufStringnioSocketChannel.pipeline().addLast(new StringDecoder());// 6、SocketChannel的业务处理使用上一个处理器的处理结果nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandlerString() {Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {System.out.println(s);}});}// 7、ServerSocketChannel绑定8080端口}).bind(8080);} } 2、客户端代码 public class HelloClient {public static void main(String[] args) throws InterruptedException {new Bootstrap().group(new NioEventLoopGroup())// 选择客户 Socket 实现类NioSocketChannel 表示基于 NIO 的客户端实现.channel(NioSocketChannel.class)// ChannelInitializer 处理器仅执行一次// 它的作用是待客户端SocketChannel建立连接后执行initChannel以便添加更多的处理器.handler(new ChannelInitializerChannel() {Overrideprotected void initChannel(Channel channel) throws Exception {// 消息会经过通道 handler 处理这里是将 String ByteBuf 编码发出channel.pipeline().addLast(new StringEncoder());}})// 指定要连接的服务器和端口.connect(new InetSocketAddress(localhost, 8080))// Netty 中很多方法都是异步的如 connect// 这时需要使用 sync 方法等待 connect 建立连接完毕.sync()// 获取 channel 对象它即为通道抽象可以进行数据读写操作.channel()// 写入消息并清空缓冲区.writeAndFlush(hello world);} } 3、运行流程 左客户端 右服务器端 组件解释 channel 可以理解为数据的通道msg 理解为流动的数据最开始输入是 ByteBuf但经过 pipeline 中的各个 handler 加工会变成其它类型对象最后输出又变成 ByteBufhandler 可以理解为数据的处理工序 工序有多道合在一起就是 pipeline传递途径pipeline 负责发布事件读、读取完成…传播给每个 handler handler 对自己感兴趣的事件进行处理重写了相应事件处理方法 pipeline 中有多个 handler处理时会依次调用其中的 handlerhandler 分 Inbound 和 Outbound 两类 Inbound 入站Outbound 出站eventLoop 可以理解为处理数据的工人 eventLoop 可以管理多个 channel 的 io 操作并且一旦 eventLoop 负责了某个 channel就会将其与channel进行绑定以后该 channel 中的 io 操作都由该 eventLoop 负责eventLoop 既可以执行 io 操作也可以进行任务处理每个 eventLoop 有自己的任务队列队列里可以堆放多个 channel 的待处理任务任务分为普通任务、定时任务eventLoop 按照 pipeline 顺序依次按照 handler 的规划代码处理数据可以为每个 handler 指定不同的 eventLoop 三、组件 1、EventLoop 事件循环对象 EventLoop EventLoop 本质是一个单线程执行器同时维护了一个 Selector里面有 run 方法处理一个或多个 Channel 上源源不断的 io 事件 它的继承关系如下 继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法继承自 netty 自己的 OrderedEventExecutor 提供了 boolean inEventLoop(Thread thread) 方法判断一个线程是否属于此 EventLoop提供了 EventLoopGroup parent() 方法来看看自己属于哪个 EventLoopGroup 事件循环组 EventLoopGroup EventLoopGroup 是一组 EventLoopChannel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop后续这个 Channel 上的 io 事件都由此 EventLoop 来处理保证了 io 事件处理时的线程安全 继承自 netty 自己的 EventExecutorGroup 实现了 Iterable 接口提供遍历 EventLoop 的能力另有 next 方法获取集合中下一个 EventLoop 处理普通与定时任务 public class TestEventLoop {public static void main(String[] args) {// 创建拥有两个EventLoop的NioEventLoopGroup对应两个线程EventLoopGroup group new NioEventLoopGroup(2);// 通过next方法可以获得下一个 EventLoopSystem.out.println(group.next());System.out.println(group.next());// 通过EventLoop执行普通任务group.next().execute(()-{System.out.println(Thread.currentThread().getName() hello);});// 通过EventLoop执行定时任务group.next().scheduleAtFixedRate(()-{System.out.println(Thread.currentThread().getName() hello2);}, 0, 1, TimeUnit.SECONDS);// 优雅地关闭整个EventLoopGroup group.shutdownGracefully();} } 输出结果如下 io.netty.channel.nio.NioEventLoop7bb11784 io.netty.channel.nio.NioEventLoop33a10788 nioEventLoopGroup-2-1 hello nioEventLoopGroup-2-2 hello2 nioEventLoopGroup-2-2 hello2 nioEventLoopGroup-2-2 hello2 关闭 EventLoopGroup 优雅关闭 shutdownGracefully 方法。该方法会首先切换 EventLoopGroup 到关闭状态从而拒绝新的任务的加入然后在任务队列的任务都处理完成后停止线程的运行。从而确保整体应用是在正常有序的状态下退出的 处理IO任务 服务器代码 public class MyServer {public static void main(String[] args) {new ServerBootstrap().group(new NioEventLoopGroup()).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializerSocketChannel() {Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf (ByteBuf) msg;System.out.println(Thread.currentThread().getName() buf.toString(StandardCharsets.UTF_8));}});}}).bind(8080);} } 客户端代码 public class MyClient {public static void main(String[] args) throws IOException, InterruptedException {Channel channel new Bootstrap().group(new NioEventLoopGroup()).channel(NioSocketChannel.class).handler(new ChannelInitializerSocketChannel() {Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new StringEncoder());}}).connect(new InetSocketAddress(localhost, 8080)).sync().channel();System.out.println(channel);// 此处打断点调试调用 channel.writeAndFlush(...);System.in.read();} } 分工细化 划分Boss 和Work  Bootstrap的group()方法可以传入两个EventLoopGroup参数分别负责处理不同的事件 public class MyServer {public static void main(String[] args) {new ServerBootstrap()// 两个Group分别为Boss 负责Accept事件Worker 负责读写事件.group(new NioEventLoopGroup(1), new NioEventLoopGroup(2))...} } 一个EventLoop可以负责多个Channel且EventLoop一旦与Channel绑定则一直负责处理该Channel中的事件 增加自定义EventLoopGroup 当有的任务需要较长的时间处理时可以使用非NioEventLoopGroup避免同一个NioEventLoop中的其他Channel在较长的时间内都无法得到处理 切换的实现 不同的EventLoopGroup切换的实现原理如下 由上面的图可以看出当handler中绑定的Group不同时需要切换Group来执行不同的任务 如果两个 handler 绑定的是同一个EventLoopGroup那么就直接调用否则把要调用的代码封装为一个任务对象由下一个 handler 的 EventLoopGroup 来调用 2、Channel Channel 的常用方法 close() 可以用来关闭ChannelcloseFuture() 用来处理 Channel 的关闭 sync 方法作用是同步等待 Channel 关闭而 addListener 方法是异步等待 Channel 关闭pipeline() 方法用于添加处理器write() 方法将数据写入 因为缓冲机制数据被写入到 Channel 中以后不会立即被发送只有当缓冲满了或者调用了flush()方法后才会将数据通过 Channel 发送出去writeAndFlush() 方法将数据写入并立即发送刷出 ChannelFuture 连接问题 ChannelFuture是Netty中的一个异步操作的结果对象它的作用是利用 channel() 方法来获取 Channel 对象。它代表了一个将来可能会拥有操作结果的操作。在Netty中几乎所有的异步操作都会返回一个ChannelFuture对象用于表示对该操作的异步执行。 如果我们去掉channelFuture.sync()方法会服务器无法收到hello world 这是因为建立连接(connect)的过程是异步非阻塞的若不通过sync()方法阻塞主线程等待连接真正建立这时通过 channelFuture.channel() 拿到的 Channel 对象并不是真正与服务器建立好连接的 Channel也就没法将信息正确的传输给服务器端 所以需要通过channelFuture.sync()方法阻塞主线程同步处理结果等待连接真正建立好以后再去获得 Channel 传递数据。使用该方法获取 Channel 和发送数据的线程都是主线程 下面还有一种方法用于异步获取建立连接后的 Channel 和发送数据使得执行这些操作的线程是 NIO 线程去执行connect操作的线程 addListener方法 通过这种方法可以在NIO线程中获取 Channel 并发送数据而不是在主线程中执行这些操作 处理关闭  关闭channel 当我们要关闭channel时可以调用channel.close()方法进行关闭。但是该方法也是一个异步方法。真正的关闭操作并不是在调用该方法的线程中执行的而是在NIO线程中执行真正的关闭操作 如果我们想在channel真正关闭以后执行一些额外的善后操作可以选择以下两种方法来实现 通过channel.closeFuture()方法获得对应的ChannelFuture对象然后调用sync()方法阻塞执行操作的线程等待channel真正关闭后再执行其他操作 // 获得closeFuture对象 ChannelFuture closeFuture channel.closeFuture();// 同步等待NIO线程执行完close操作 closeFuture.sync(); 调用closeFuture.addListener方法添加close的后续操作 closeFuture.addListener(new ChannelFutureListener() {Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {// 等待channel关闭后才执行的操作System.out.println(关闭之后执行一些额外操作...);// 关闭EventLoopGroupgroup.shutdownGracefully();} }); 为什么Netty要将多个API调用NIO线程来异步实现 其实这个可以和JVM的指令重排优化这个知识点挂钩主要是为了最大程度发挥每个线程对于单一操作的一个满负荷性充分利用CPU提高吞吐量 3、Future与Promise 概念 Future未来是一个并发编程的概念用于表示一个异步操作的结果尚未完成的值或异常。在编程中Future可以作为一个占位符代表一个将来可能会获得的值。 netty 中的 Future 与 jdk 中的 Future 同名但是是两个接口 netty 的 Future 继承自 jdk 的 Future而 Promise 又对 netty Future 进行了扩展 jdk Future 只能同步等待任务结束或成功、或失败才能得到结果netty Future 可以同步等待任务结束得到结果也可以异步方式得到结果但都是要等任务结束netty Promise 不仅有 netty Future 的功能而且脱离了任务独立存在只作为两个线程间传递结果的容器 功能/名称jdk Futurenetty FuturePromisecancel取消任务--isCanceled任务是否取消--isDone任务是否完成不能区分成功失败--get获取任务结果阻塞等待--getNow-获取任务结果非阻塞还未产生结果时返回 null-await-等待任务结束如果任务失败不会抛异常而是通过 isSuccess 判断-sync-等待任务结束如果任务失败抛出异常-isSuccess-判断任务是否成功-cause-获取失败信息非阻塞如果没有失败返回null-addLinstener-添加回调异步接收结果-setSuccess--设置成功结果setFailure--设置失败结果 JDK Future public class JdkFuture {public static void main(String[] args) throws ExecutionException, InterruptedException {ThreadFactory factory new ThreadFactory() {Overridepublic Thread newThread(Runnable r) {return new Thread(r, JdkFuture);}};// 创建线程池ThreadPoolExecutor executor new ThreadPoolExecutor(5, 10,10, TimeUnit.SECONDS, new ArrayBlockingQueue(10), factory);// 获得Future对象FutureInteger future executor.submit(new CallableInteger() {Overridepublic Integer call() throws Exception {TimeUnit.SECONDS.sleep(1);return 50;}});// 通过阻塞的方式获得运行结果System.out.println(future.get());} } Netty Future Netty的Future与JDK的Future有一些优化和扩展主要体现在异步操作支持Netty的Future支持异步操作允许在执行网络操作时立即返回并继续执行其他任务而不需要阻塞等待操作完成。这种异步操作使得Netty在处理高并发和大规模网络请求时表现出色。 public class NettyFuture {public static void main(String[] args) throws ExecutionException, InterruptedException {NioEventLoopGroup group new NioEventLoopGroup();// 获得 EventLoop 对象EventLoop eventLoop group.next();FutureInteger future eventLoop.submit(new CallableInteger() {Overridepublic Integer call() throws Exception {return 50;}});// 主线程中获取结果System.out.println(Thread.currentThread().getName() 获取结果);System.out.println(getNow future.getNow());System.out.println(get future.get());// NIO线程中异步获取结果future.addListener(new GenericFutureListenerFuture? super Integer() {Overridepublic void operationComplete(Future? super Integer future) throws Exception {System.out.println(Thread.currentThread().getName() 获取结果);System.out.println(getNow future.getNow());}});} } 采用这种异步获取返回值处理的方案适用于主线程要执行的业务与该返回值无关但是该返回值又不得不进行一个处理的情况。所以说将这个活交给小弟线程异步来处理掉主线程可以安心的处理自己的事 Netty中的Future对象可以通过EventLoop的sumbit()方法得到 可以通过Future对象的get方法阻塞地获取返回结果也可以通过getNow方法获取结果若还没有结果则返回null该方法是非阻塞的还可以通过future.addListener方法在Callable方法执行的线程中异步获取返回结果 Netty Promise Promise相当于一个容器可以用于存放各个线程中的结果然后让其他线程去获取该结果 把多线程比作分布式的环境那么Promise就是Redis这种第三方存储结构通过独立于架构外的存储可以实现各线程间的数据交换有点类似于前端组件传播的全局事务总线 public class NettyPromise {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建EventLoopNioEventLoopGroup group new NioEventLoopGroup();EventLoop eventLoop group.next();// 创建Promise对象用于存放结果DefaultPromiseInteger promise new DefaultPromise(eventLoop);new Thread(()-{try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 自定义线程向Promise中存放结果promise.setSuccess(50);}).start();// 主线程从Promise中获取结果System.out.println(Thread.currentThread().getName() promise.get());} } Promise提供了setResult()和setFailure()方法可以主动设置操作的结果或异常。这使得在一些特殊情况下可以人为地指定操作的结果而不需要等待真正的异步操作完成。例如可以在操作超时时主动设置Promise的结果。 romise的setFailure()方法可以设置操作的异常在Future中可以通过cause()来获取异常。这使得异常处理变得更加便捷和清晰可以更好地处理操作的失败情况。 4、Handler与Pipeline Pipeline public class PipeLineServer {public static void main(String[] args) {new ServerBootstrap().group(new NioEventLoopGroup()).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializerSocketChannel() {Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {// 在socketChannel的pipeline中添加handler// pipeline中handler是带有head与tail节点的双向链表的实际结构为// head - handler1 - ... - handler4 -tail// Inbound主要处理入站操作一般为读操作发生入站操作时会触发Inbound方法// 入站时handler是从head向后调用的socketChannel.pipeline().addLast(handler1 ,new ChannelInboundHandlerAdapter() {Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println(Thread.currentThread().getName() Inbound handler 1);String newResult msg wzx;// 父类该方法内部会调用fireChannelRead// 将数据传递给下一个handlersuper.channelRead(ctx, newResult );}});socketChannel.pipeline().addLast(handler2, new ChannelInboundHandlerAdapter() {Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println(Thread.currentThread().getName() Inbound handler 2);// 执行write操作使得Outbound的方法能够得到调用如果channel中没有写入的数据Outbound的方法是不会发生调用的socketChannel.writeAndFlush(ctx.alloc().buffer().writeBytes(Server....getBytes(StandardCharsets.UTF_8)));}});// Outbound主要处理出站操作一般为写操作发生出站操作时会触发Outbound方法// 出站时handler的调用是从tail向前调用的socketChannel.pipeline().addLast(handler3 ,new ChannelOutboundHandlerAdapter(){Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {System.out.println(Thread.currentThread().getName() Outbound handler 1);super.write(ctx, msg, promise);}});socketChannel.pipeline().addLast(handler4 ,new ChannelOutboundHandlerAdapter(){Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {System.out.println(Thread.currentThread().getName() Outbound handler 2);super.write(ctx, msg, promise);}});}}).bind(8080);} } 通过channel.pipeline().addLast(name, handler)添加handler时记得给handler取名字。这样可以调用pipeline的addAfter、addBefore等方法更灵活地向pipeline中添加handler handler需要放入通道的pipeline中才能根据放入顺序来使用handler pipeline是结构是一个带有head与tail指针的双向链表其中的节点为handler 要通过ctx.fireChannelRead(msg)等方法将当前handler的处理结果传递给下一个handler当有入站Inbound操作时会从head开始向后调用handler直到handler不是处理Inbound操作为止当有出站Outbound操作时会从tail开始向前调用handler直到handler不是处理Outbound操作为止 具体结构如下 调用顺序如下 OutboundHandler socketChannel.writeAndFlush() 当handler中调用该方法进行写操作时会触发Outbound操作此时是从tail向前寻找OutboundHandler ctx.writeAndFlush() ChannelHandlerContext是Netty中的一个重要概念它代表了一个网络通信的上下文环境。每当有新的连接建立或数据传输时Netty会为每个连接创建一个对应的ChannelHandlerContext对象。         ChannelHandlerContext包含了与通信相关的各种信息包括底层的Channel、ChannelPipeline、处理器Handler等。它提供了操作和管理网络通信的方法例如数据的读写、发送消息、关闭连接等。         通过ChannelHandlerContext我们可以访问和操作与特定连接相关的所有资源和状态。它允许我们编写自定义的处理器来实现特定的业务逻辑对接收到的数据进行处理并将相应的响应发送回客户端。 当handler中调用该方法进行写操作时会触发Outbound操作此时是从当前handler向前寻找OutboundHandler EmbeddedChannel EmbeddedChannel可以用于测试各个handler通过其构造函数按顺序传入需要测试handler然后调用对应的Inbound和Outbound方法即可 public class TestEmbeddedChannel {public static void main(String[] args) {ChannelInboundHandlerAdapter h1 new ChannelInboundHandlerAdapter() {Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println(1);super.channelRead(ctx, msg);}};ChannelInboundHandlerAdapter h2 new ChannelInboundHandlerAdapter() {Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println(2);super.channelRead(ctx, msg);}};ChannelOutboundHandlerAdapter h3 new ChannelOutboundHandlerAdapter() {Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {System.out.println(3);super.write(ctx, msg, promise);}};ChannelOutboundHandlerAdapter h4 new ChannelOutboundHandlerAdapter() {Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {System.out.println(4);super.write(ctx, msg, promise);}};// 用于测试Handler的ChannelEmbeddedChannel channel new EmbeddedChannel(h1, h2, h3, h4);// 执行Inbound操作 channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes(hello.getBytes(StandardCharsets.UTF_8)));// 执行Outbound操作channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes(hello.getBytes(StandardCharsets.UTF_8)));} } 5、ByteBuf 创建 public class ByteBufStudy {public static void main(String[] args) {// 创建ByteBufByteBuf buffer ByteBufAllocator.DEFAULT.buffer(16);ByteBufUtil.log(buffer);// 向buffer中写入数据StringBuilder sb new StringBuilder();for(int i 0; i 20; i) {sb.append(a);}buffer.writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8));// 查看写入结果ByteBufUtil.log(buffer);} } ByteBuf通过ByteBufAllocator选择allocator并调用对应的buffer()方法来创建的默认使用直接内存作为ByteBuf容量为256个字节可以指定初始容量的大小 当ByteBuf的容量无法容纳所有数据时ByteBuf会进行动态扩容操作(扩容为当前容量的两倍) 如果在handler中创建ByteBuf建议使用ChannelHandlerContext 的ctx.alloc().buffer()来创建 直接内存与堆内存 通过该方法创建的ByteBuf使用的是基于直接内存的ByteBuf ByteBuf buffer ByteBufAllocator.DEFAULT.buffer(16); 可以使用下面的代码来创建池化基于堆的 ByteBuf ByteBuf buffer ByteBufAllocator.DEFAULT.heapBuffer(16); 也可以使用下面的代码来创建池化基于直接内存的 ByteBuf ByteBuf buffer ByteBufAllocator.DEFAULT.directBuffer(16); 直接内存创建和销毁的代价昂贵但读写性能高少一次内存复制适合配合池化功能一起用直接内存对 GC 压力小因为这部分内存不受 JVM 垃圾回收的管理但也要注意及时主动释放 池化与非池化 池化的最大意义在于可以重用 ByteBuf优点有 没有池化则每次都得创建新的 ByteBuf 实例这个操作对直接内存代价昂贵就算是堆内存也会增加 GC 压力有了池化则可以重用池中 ByteBuf 实例并且采用了与 jemalloc 类似的内存分配算法提升分配效率高并发时池化功能更节约内存减少内存溢出的可能 在高并发环境下池化功能可以在一定程度上节约内存并减少内存溢出的可能性。这是因为池化功能通过对资源的重用减少了频繁地创建和销毁对象的开销。         具体来说当系统面临高并发请求时如果每次请求都需要创建一个新的对象来处理那么系统就需要频繁地进行内存分配和释放操作。这会导致内存碎片的产生浪费大量的内存空间并且频繁的对象创建和销毁会消耗较多的CPU时间。         而使用池化功能可以事先创建一定数量的对象并将其存储在对象池中。当有请求到达时可以从对象池中获取一个可用的对象使用完毕后再将其放回池中进行重用。这样就避免了频繁地创建和销毁对象的过程。         综上所述池化功能可以在高并发环境下提供更好的资源管理和利用效率节约内存并减少内存溢出的可能性。 池化功能是否开启可以通过下面的系统环境变量来设置 -Dio.netty.allocator.type{unpooled|pooled} 4.1 以后非 Android 平台默认启用池化实现Android 平台启用非池化实现4.1 之前池化功能还不成熟默认是非池化实现 组成 ByteBuf主要有以下几个组成部分 最大容量与当前容量 在构造ByteBuf时可传入两个参数分别代表初始容量和最大容量若未传入第二个参数最大容量最大容量默认为Integer.MAX_VALUE当ByteBuf容量无法容纳所有数据时会进行扩容操作若超出最大容量会抛出java.lang.IndexOutOfBoundsException异常读写操作不同于ByteBuffer只用position进行控制ByteBuf分别由读指针和写指针两个指针控制。进行读写操作时无需进行模式的切换 读指针前的部分被称为废弃部分是已经读过的内容读指针与写指针之间的空间称为可读部分写指针与当前容量之间的空间称为可写部分 写入 常用方法如下 方法签名含义备注writeBoolean(boolean value)写入 boolean 值用一字节 01|00 代表 true|falsewriteByte(int value)写入 byte 值writeShort(int value)写入 short 值writeInt(int value)写入 int 值Big Endian大端写入即 0x250写入后 00 00 02 50writeIntLE(int value)写入 int 值Little Endian小端写入即 0x250写入后 50 02 00 00writeLong(long value)写入 long 值writeChar(int value)写入 char 值writeFloat(float value)写入 float 值writeDouble(double value)写入 double 值writeBytes(ByteBuf src)写入 netty 的 ByteBufwriteBytes(byte[] src)写入 byte[]writeBytes(ByteBuffer src)写入 nio 的 ByteBufferint writeCharSequence(CharSequence sequence, Charset charset)写入字符串CharSequence为字符串类的父类第二个参数为对应的字符集 注意 这些方法的未指明返回值的其返回值都是 ByteBuf意味着可以链式调用来写入不同的数据网络传输中默认习惯是 Big Endian使用 writeInt(int value) 使用方法 public class ByteBufStudy {public static void main(String[] args) {// 创建ByteBufByteBuf buffer ByteBufAllocator.DEFAULT.buffer(16, 20);ByteBufUtil.log(buffer);// 向buffer中写入数据buffer.writeBytes(new byte[]{1, 2, 3, 4});ByteBufUtil.log(buffer);buffer.writeInt(5);ByteBufUtil.log(buffer);buffer.writeIntLE(6);ByteBufUtil.log(buffer);buffer.writeLong(7);ByteBufUtil.log(buffer);} } 扩容 当ByteBuf中的容量无法容纳写入的数据时会进行扩容操作 扩容因子对于HeapByteBuf和DirectByteBuf默认的扩容规则是按照2的幂次方进行扩容。即每次扩容时会将容量扩大到原容量的两倍以提供更多的空间来存储数据。这种扩容规则在大多数情况下都能够提供较好的内存利用效率。 固定长度对于CompositeByteBuf其容量是固定的无法进行扩容。CompositeByteBuf是由多个ByteBuf组成的复合缓冲区每个ByteBuf具有单独的容量并负责存储对应的数据。 读取 读取主要是通过一系列read方法进行读取读取时会根据读取数据的字节数移动读指针 如果需要重复读取需要调用buffer.markReaderIndex()对读指针进行标记并通过buffer.resetReaderIndex()将读指针恢复到mark标记的位置 public class ByteBufStudy {public static void main(String[] args) {// 创建ByteBufByteBuf buffer ByteBufAllocator.DEFAULT.buffer(16, 20);// 向buffer中写入数据buffer.writeBytes(new byte[]{1, 2, 3, 4});buffer.writeInt(5);// 读取4个字节System.out.println(buffer.readByte());System.out.println(buffer.readByte());System.out.println(buffer.readByte());System.out.println(buffer.readByte());ByteBufUtil.log(buffer);// 通过mark与reset实现重复读取buffer.markReaderIndex();System.out.println(buffer.readInt());ByteBufUtil.log(buffer);// 恢复到mark标记处buffer.resetReaderIndex();ByteBufUtil.log(buffer);} } 还有以 get 开头的一系列方法这些方法不会改变读指针的位置 释放 由于 Netty 中有堆外内存直接内存的 ByteBuf 实现堆外内存最好是手动来释放而不是等 GC 垃圾回收。 UnpooledHeapByteBuf 使用的是 JVM 内存只需等 GC 回收内存即可UnpooledDirectByteBuf 使用的就是直接内存了需要特殊的方法来回收内存采用虚引用 Clean.clean()方法调用本地方法unsafe.freeMemory()来释放直接内存PooledByteBuf 和它的子类使用了池化机制需要更复杂的规则来回收内存 Netty 这里采用了引用计数法来控制回收内存每个 ByteBuf 都实现了 ReferenceCounted 接口 每个 ByteBuf 对象的初始计数为 1调用 release 方法计数减 1如果计数为 0ByteBuf 内存被回收调用 retain 方法计数加 1表示调用者没用完之前其它 handler 即使调用了 release 也不会造成回收当计数为 0 时底层内存会被回收这时即使 ByteBuf 对象还在其各个方法均无法正常使用 释放规则 因为 pipeline 的存在一般需要将 ByteBuf 传递给下一个 ChannelHandler如果在每个 ChannelHandler 中都去调用 release 就失去了传递性如果在这个 ChannelHandler 内这个 ByteBuf 已完成了它的使命那么便无须再传递 基本规则是谁是最后使用者谁负责 release 起点对于 NIO 实现来讲在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe.read 方法中首次创建 ByteBuf 放入 pipelineline 163 pipeline.fireChannelRead(byteBuf) 入站 ByteBuf 处理原则 对原始 ByteBuf 不做处理调用 ctx.fireChannelRead(msg) 向后传递这时无须 release将原始 ByteBuf 转换为其它类型的 Java 对象这时 ByteBuf 就没用了必须 release如果不调用 ctx.fireChannelRead(msg) 向后传递那么也必须 release注意各种异常如果 ByteBuf 没有成功传递到下一个 ChannelHandler必须 release假设消息一直向后传那么 TailContext 会负责释放未处理消息原始的 ByteBuf 出站 ByteBuf 处理原则 出站消息最终都会转为 ByteBuf 输出一直向前传由 HeadContext flush 后 release 异常处理原则 有时候不清楚 ByteBuf 被引用了多少次但又必须彻底释放可以循环调用 release 直到返回 true while (!buffer.release()) {} 当ByteBuf被传到了pipeline的head与tail时ByteBuf会被其中的方法彻底释放但前提是ByteBuf被传递到了head与tail中 切片 ByteBuf切片是【零拷贝】的体现之一对原始 ByteBuf 进行切片成多个 ByteBuf切片后的 ByteBuf 并没有发生内存复制还是使用原始 ByteBuf 的内存切片后的 ByteBuf 维护独立的 readwrite 指针 得到分片后的buffer后要调用其retain方法使其内部的引用计数加一。避免原ByteBuf释放导致切片buffer无法使用 修改原ByteBuf中的值也会影响切片后得到的ByteBuf public class TestSlice {public static void main(String[] args) {// 创建ByteBufByteBuf buffer ByteBufAllocator.DEFAULT.buffer(16, 20);// 向buffer中写入数据buffer.writeBytes(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});// 将buffer分成两部分 参数1下标 参数2长度ByteBuf slice1 buffer.slice(0, 5);ByteBuf slice2 buffer.slice(5, 5);// 需要让分片的buffer引用计数加一// 避免原Buffer释放导致分片buffer无法使用slice1.retain();slice2.retain();ByteBufUtil.log(slice1);ByteBufUtil.log(slice2);// 更改原始buffer中的值System.out.println(修改原buffer中的值);// 修改0下标元素为5buffer.setByte(0,5);System.out.println(打印slice1);ByteBufUtil.log(slice1);} } 运行结果 注意 可以在切片中修改元素但无法直接新增元素或删除元素这需要通过原始ByteBuf进行操作。切片和原有的ByteBuf共用一份内存收到同样的引用计数法来控制回收内存就是说释放了依次原来的ByteBuf那么切片中的计数也会减一为了避免这种影响需要让分片的buffer引用计数加一
http://www.tj-hxxt.cn/news/130137.html

相关文章:

  • 网网站建设公司咨询最全黄页
  • 网站域名备案证书下载网站做代练
  • 青岛海川建设集团网站公众号制作教程视频
  • 中国建设银行英文网站安庆网站建设电话
  • 仿淘宝电商网站开发报价企业网站建设太原网站建设
  • js做网站登录界面装修网页
  • 周大福网站建设主要工作有哪些公司建设网站
  • 网站怎么做更新互联网营销师培训教程
  • 南部县建设局网站wordpress教程创建网页
  • 网站建设与维护费套路网站怎么做的
  • 网络营销的主要形式有建设网站市场营销方案案例范文
  • 云南网站开发培训机构专业网站建设价格
  • 上海的网站开发公司wordpress评论去掉网址
  • 站长之家ping检测别墅装修装饰
  • 网站制作 沈阳网页制作源代码免费的
  • 惠州seo网站管理谷歌官网登录入口
  • 做网站不需要编程的软件合购WordPress
  • 苏州做物流网站电话主流网站编程语言
  • 太原网站空间wordpress做seo合适吗
  • 网站企业网站建设需求文档网络软件开发专业是做什么的
  • 营销网站建设都是专业技术人员柳州论坛网站建设
  • 聊城手机网站建设电话商丘建设网站
  • 顺德销售型网站建设网站建设分为哪几部分
  • 最流行的网站设计风格做牛仔裤的视频网站
  • 注册网站域名的入口是建个网站有什么用
  • 网站链接如何做二维码wordpress hta
  • 网站开发公司分析哈尔滨市哪里做淘宝网站
  • 贵州软件开发 网站开发php装修公司网站源码
  • 网站建设微商城多少钱id怎么编辑wordpress
  • 苏州网站建设及推广网站建设的需求文档