16.Netty源码之ChannelPipeline
highlight: arduino-light
服务编排层:ChannelPipeline协调ChannelHandlerHandler
EventLoop可以说是 Netty 的调度中心,负责监听多种事件类型:I/O 事件、信号事件、定时事件等,然而实际的业务处理逻辑则是由 ChannelPipeline 中所定义的 ChannelHandler 完成的,ChannelPipeline 和 ChannelHandler应用开发的过程中打交道最多的组件。
Netty 服务编排层的核心组件 ChannelPipeline 和 ChannelHandler 为用户提供了 I/O 事件的全部控制权。
在学习之前,先思考几个问题。
- ChannelPipeline 与 ChannelHandler 的关系是什么?它们之间是如何协同工作的?
- ChannelHandler 的类型有哪些?有什么区别?
- Netty 中 I/O 事件是如何传播的?
ChannelPipeline 概述
Pipeline 的字面意思是管道、流水线。它在 Netty 中起到的作用,和一个工厂的流水线类似。
原始的网络字节流经过 Pipeline ,被一步步加工包装,最后得到加工后的成品。
经过前面课程核心组件的初步学习,我们已经对 ChannelPipeline 有了初步的印象:它是 Netty 的核心处理链,用以实现网络事件的动态编排和有序传播。
今天我们将从以下几个方面一起探讨 ChannelPipeline 的实现原理:
- ChannelPipeline 内部结构;
- ChannelHandler 接口设计;
- ChannelPipeline 事件传播机制;
- ChannelPipeline 异常传播机制。
ChannelPipeline 内部结构
ChannelPipeline 可以看作是 ChannelHandler 的容器载体,它是由一组 ChannelHandler 实例组成的,内部通过双向链表将不同的 ChannelHandler 链接在一起,如下图所示。
当有 I/O 读写事件触发时,ChannelPipeline 会依次调用 ChannelHandler 列表对 Channel 的数据进行拦截和处理。
每个 ChannelHandler 都对应一个 ChannelHandlerContext。
所以实际上 ChannelPipeline 维护的是它与 ChannelHandlerContext 的关系。
那么你可能会有疑问,为什么这里会多一层 ChannelHandlerContext 的封装呢?
其实这是一种比较常用的编程思想。ChannelHandlerContext用于保存ChannelHandler。
ChannelHandlerContext包含了ChannelHandler生命周期的所有事件,如 connect、bind、read、 flush、write、close 等。
可以试想一下,如果没有ChannelHandlerContext 的这层封装,那么我们在做 ChannelHandler 之间传递的时 候。前置后置的通用逻辑就要在每个 ChannelHandler 里都实现一份。
这样虽然能解决问题,但是代码结构的耦合,会非常不优雅。
根据网络数据的流向,ChannelPipeline 分为入站 ChannelInboundHandler和出站 ChannelOutboundHandler。
服务端接收到客户端数据需要先经过 Decoder 入站处理后,再通过 Encoder 出站通知客户端。
ChannelPipeline是双向链表的构造。
ChannelPipeline 的双向链表分别维护了HeadContext 和 TailContext 的头尾节点。
我们自定义的ChannelHandler会插入到 Head 和 Tail 之间,这两个节点在 Netty 中已经默认实现了,它们在
ChannelPipeline 中起到了至关重要的作用。
首先我们看下 HeadContext 和 TailContext 的继承关系,如下图所示。
HeadContext:in\&out
对于1个请求先由HeadContext处理入栈,经过一系列的入栈处理器然后传递到TailContext,由TailContext往下传递经过一系列的出栈处理器,最后再经过HeadContext返回。 ```md
<---6 <---5 <---4 HeadContext InBoundHandlerOne TailContext OutBoundHandlerOne 1---> 2---> 3--->
顺序是:12346 其中5是入栈已经在2处理过 所以不需要处理。 ```
Inbound第一站
Outbound最后一站
HeadContext 既是 Inbound 处理器,也是 Outbound 处理器。 它分别实现了 ChannelInboundHandler 和 ChannelOutboundHandler。
网络数据写入操作的入口就是由 HeadContext 节点完成的。HeadContext 作为 Pipeline 的头结点负责读取数据并开始传递 InBound 事件,当数据处理完成后,数据会反方向经过 Outbound 处理器,最终传递到 HeadContext,所以 HeadContext 既是数据读取Inbound事件的第一站又是处理 Outbound 事件的最后一站。
此外 HeadContext 在传递事件之前,还会执行一些前置操作。
TailContext
Outbound第一站
TailContext 只实现了 ChannelInboundHandler 接口。它会在 ChannelInboundHandler 调用链路的最后一步执行,主要用于终止 Inbound 事件传播,例如释放 Message 数据资源等。
TailContext 节点作为 OutBound 事件传播的第一站,仅仅是将 OutBound 事件传递给下一个节点。
从整个 ChannelPipeline 调用链路来看,如果由 Channel 直接触发事件传播,那么调用链路将贯穿整个 ChannelPipeline。
然而也可以在其中某一个 ChannelHandlerContext 触发同样的方法,这样只会从当前的 ChannelHandler 开始执行事件传播,该过程不会从头贯穿到尾,在一定场景下,可以提高程序性能。
ChannelHandler 接口设计
在学习 ChannelPipeline 事件传播机制之前,我们需要了解 I/O 事件的生命周期。
整个 ChannelHandler 是围绕 I/O 事件的生命周期所设计的,例如建立连接、读数据、写数据、连接销毁等。
ChannelHandler 有两个重要的子接口:ChannelInboundHandler和ChannelOutboundHandler,分别拦截入
站和出站的各种 I/O 事件。
1. ChannelInboundHandler 的事件回调方法与触发时机。
| 事件回调方法 | 触发时机 | | ------------------------- | --------------------------------- | | channelRegistered | Channel 被注册到 EventLoop | | channelUnregistered | Channel 从 EventLoop 中取消注册 | | channelActive | Channel 处于就绪状态,可以被读写 | | channelInactive | Channel 处于非就绪状态Channel 可以从远端读取到数据 | | channelRead | Channel 可以从远端读取到数据 | | channelReadComplete | Channel 读取数据完成 | | userEventTriggered | 用户事件触发时 | | channelWritabilityChanged | Channel 的写状态发生变化 | | handlerAdded | 当该处理器被添加到pipeline时 |
2. ChannelOutboundHandler 的事件回调方法与触发时机。
ChannelOutboundHandler 的事件回调方法非常清晰,直接通过 ChannelOutboundHandler 的接口列表可以看到每种操作所对应的回调方法,如下图所示。
这里每个回调方法都是在相应操作执行之前触发,在此就不多做赘述了。
此外 ChannelOutboundHandler 中绝大部分接口都包含ChannelPromise 参数,以便于在操作完成时能够及时获得通知。
事件
事件枚举
public static final int OP_READ = 1 << 0;//1
public static final int OP_WRITE = 1 << 2;//4
public static final int OP_CONNECT = 1 << 3;//8
public static final int OP_ACCEPT = 1 << 4;//16
事件传播机制
在上文中我们介绍了 ChannelPipeline 可分为入站 ChannelInboundHandler 和出站 ChannelOutboundHandler 两种处理器,与此对应传输的事件类型可以分为Inbound 事件和Outbound 事件。
我们通过一个代码示例,一起体验下 ChannelPipeline 的事件传播机制。 ```java package io.netty.example.pipeline;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;/*** Echoes back any received data from a client.*/
public final class EchoServer {public static void main(String[] args) throws Exception {EventLoopGroup workerGroup = new NioEventLoopGroup();EventLoopGroup bossGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup)//通过反射创建反射工厂类根据无参构造函数 反射生成实例//将NioServerSocketChannel绑定到了bossGroup//NioServerSocketChannel接收到请求会创建SocketChannel放入workerGroup.channel(NioServerSocketChannel.class)//指的是SocketChannel.childOption(ChannelOption.SO_KEEPALIVE, true)//指的是SocketChannel.childOption(NioChannelOption.SO_KEEPALIVE, Boolean.TRUE)//默認不使用堆外内存.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)//false 不使用堆外内存.childOption(ChannelOption.ALLOCATOR, new UnpooledByteBufAllocator(false))// .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();//正常情况p.addLast("SampleInBoundHandlerA", new SampleInBoundHandler("SampleInBoundHandlerA", false));p.addLast("SampleInBoundHandlerB", new SampleInBoundHandler("SampleInBoundHandlerB", false));p.addLast("SampleInBoundHandlerC", new SampleInBoundHandler("SampleInBoundHandlerC", true));p.addLast("SampleOutBoundHandlerA", new SampleOutBoundHandler("SampleOutBoundHandlerA"));p.addLast("SampleOutBoundHandlerB", new SampleOutBoundHandler("SampleOutBoundHandlerB"));p.addLast("SampleOutBoundHandlerC", new SampleOutBoundHandler("SampleOutBoundHandlerC"));}});ChannelFuture f = b.bind(8090).sync();f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();}}
}
package io.netty.example.pipeline;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;public final class EchoClient {public static void main(String[] args) throws Exception {// Configure the client.EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();p.addLast(new EchoClientHandler());}});// Start the client.ChannelFuture f = b.connect("127.0.0.1", 8090).sync();// Wait until the connection is closed.f.channel().closeFuture().sync();} finally {// Shut down the event loop to terminate all threads.group.shutdownGracefully();}}
}
package io.netty.example.pipeline;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;import java.util.concurrent.TimeUnit;/*** Handler implementation for the echo client. It initiates the ping-pong* traffic between the echo client and server by sending the first message to* the server.*/
public class EchoClientHandler extends ChannelInboundHandlerAdapter {private final ByteBuf firstMessage;/*** Creates a client-side handler.*/public EchoClientHandler() {firstMessage = Unpooled.wrappedBuffer("I am echo message".getBytes());}@Overridepublic void channelActive(ChannelHandlerContext ctx) {System.out.println("客户端发送消息" + firstMessage.toString());ctx.writeAndFlush(firstMessage);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {// ctx.write(msg);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws InterruptedException {TimeUnit.SECONDS.sleep(3);ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// cause.printStackTrace();ctx.close();}
}
package io.netty.example.pipeline;import io.netty.channel.*;public class SampleInBoundHandler extends ChannelInboundHandlerAdapter {private final String name;private final boolean flush;public SampleInBoundHandler(String name, boolean flush) {this.name = name;this.flush = flush;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//打印消息System.out.println("InBoundHandler: " + name);//只有是true的时候才会写消息//否则只会读消息if (flush) {ctx.channel().writeAndFlush(msg);} else {super.channelRead(ctx, msg);}}
}
package io.netty.example.pipeline;import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;public class SampleOutBoundHandler extends ChannelOutboundHandlerAdapter {private final String name;public SampleOutBoundHandler(String name) {this.name = name;}@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {System.out.println("OutBoundHandler: " + name);super.write(ctx, msg, promise);}
}
```
通过 Pipeline 的 addLast 方法分别添加了三个 InboundHandler 和 OutboundHandler,添加顺序都是 A -> B -> C,下图可以表示初始化后 ChannelPipeline 的内部结构。
*当客户端向服务端发送请求时,会触发 SampleInBoundHandler 调用链的 channelRead 事件。经过 SampleInBoundHandler 调用链处理完成后,在 SampleInBoundHandlerC 中会调用 writeAndFlush 方法向客户端写回数据,此时会触发 SampleOutBoundHandler 调用链的 write 事件。** 最后我们看下代码示例的控制台输出:
方向:IN先进先出OUT:先进后出
由此可见,Inbound 事件和 Outbound 事件的传播方向是不一样的。Inbound 事件的传播方向为 Head -> Tail,而 Outbound 事件传播方向是 Tail -> Head,两者恰恰相反。 在 Netty 应用编程中一定要理清楚事件传播的顺序。推荐你在系统设计时模拟客户端和服务端的场景画出 ChannelPipeline 的内部结构图,以避免搞混调用关系。
异常传播机制
ChannelPipeline 事件传播的实现采用了经典的责任链模式,调用链路环环相扣。那么如果有一个节点处理逻辑异常会出现什么现象呢?我们通过修改 SampleInBoundHandler 的实现来模拟业务逻辑异常: ```java package io.netty.example.pipeline;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;/*** Echoes back any received data from a client.*/
public final class EchoServer {public static void main(String[] args) throws Exception {EventLoopGroup workerGroup = new NioEventLoopGroup();EventLoopGroup bossGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup)//通过反射创建反射工厂类根据无参构造函数 反射生成实例//将NioServerSocketChannel绑定到了bossGroup//NioServerSocketChannel接收到请求会创建SocketChannel放入workerGroup.channel(NioServerSocketChannel.class)//指的是SocketChannel.childOption(ChannelOption.SO_KEEPALIVE, true)//指的是SocketChannel.childOption(NioChannelOption.SO_KEEPALIVE, Boolean.TRUE)//默認不使用堆外内存.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)//false 不使用堆外内存.childOption(ChannelOption.ALLOCATOR, new UnpooledByteBufAllocator(false))// .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();//正常情况/*p.addLast("SampleInBoundHandlerA", new SampleInBoundHandler("SampleInBoundHandlerA", false));p.addLast("SampleInBoundHandlerB", new SampleInBoundHandler("SampleInBoundHandlerB", false));p.addLast("SampleInBoundHandlerC", new SampleInBoundHandler("SampleInBoundHandlerC", true));*/p.addLast("SampleInBoundHandlerA", new SampleExceptionInBoundHandler("SampleInBoundHandlerA", false));p.addLast("SampleInBoundHandlerB", new SampleExceptionInBoundHandler("SampleInBoundHandlerB", false));p.addLast("SampleInBoundHandlerC", new SampleExceptionInBoundHandler("SampleInBoundHandlerC", true));p.addLast("SampleOutBoundHandlerA", new SampleOutBoundHandler("SampleOutBoundHandlerA"));p.addLast("SampleOutBoundHandlerB", new SampleOutBoundHandler("SampleOutBoundHandlerB"));p.addLast("SampleOutBoundHandlerC", new SampleOutBoundHandler("SampleOutBoundHandlerC"));}});ChannelFuture f = b.bind(8090).sync();f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();}}
}
package io.netty.example.pipeline;import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;public class SampleExceptionInBoundHandler extends ChannelInboundHandlerAdapter {private final String name;private final boolean flush;public SampleExceptionInBoundHandler(String name, boolean flush) {this.name = name;this.flush = flush;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("InBoundHandler: " + name);if (flush) {ctx.channel().writeAndFlush(msg);} else {throw new RuntimeException("InBoundHandler: " + name);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {System.out.println("InBoundHandlerException: " + name);ctx.fireExceptionCaught(cause);}
}
``` 在SampleExceptionInBoundHandler的 channelRead 事件处理中,第一个 A 节点就会抛出 RuntimeException。同时我们重写了 ChannelInboundHandlerAdapter 中的 exceptionCaught 方法,只是在开头加上了控制台输出,方便观察异常传播的行为。下面看一下代码运行的控制台输出结果:
由输出结果可以看出 ctx.fireExceptionCaugh 会将异常按顺序从 Head 节点传播到 Tail 节点。
如果用户没有对异常进行拦截处理,最后将由 Tail 节点统一处理,在 TailContext 源码中可以找到具体实现:
protected void onUnhandledInboundException(Throwable cause) {try {logger.warn("An exceptionCaught() event was fired, and it reached at the tail of the pipeline. " + "It usually means the last handler in the pipeline did not handle the exception.",cause);} finally {ReferenceCountUtil.release(cause);}
}
虽然 Netty 中 TailContext 提供了兜底的异常处理逻辑,但是在很多场景下,并不能满足我们的需求。假如你需要拦截指定的异常类型,并做出相应的异常处理,应该如何实现呢?我们接着往下看。
异常处理的最佳实践
在 Netty 应用开发的过程中,良好的异常处理机制会让排查问题的过程事半功倍。所以推荐用户对异常进行统一拦截,然后根据实际业务场景实现更加完善的异常处理机制。
通过异常传播机制的学习,我们应该可以想到最好的方法是在 ChannelPipeline 自定义处理器的末端添加统一的异常处理器,此时 ChannelPipeline 的内部结构如下图所示。
用户自定义的异常处理器代码示例如下: ```java package io.netty.example.pipeline;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;/*** Echoes back any received data from a client.*/
public final class EchoServer {public static void main(String[] args) throws Exception {EventLoopGroup workerGroup = new NioEventLoopGroup();EventLoopGroup bossGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup)//通过反射创建反射工厂类根据无参构造函数 反射生成实例//将NioServerSocketChannel绑定到了bossGroup//NioServerSocketChannel接收到请求会创建SocketChannel放入workerGroup.channel(NioServerSocketChannel.class)//指的是SocketChannel.childOption(ChannelOption.SO_KEEPALIVE, true)//指的是SocketChannel.childOption(NioChannelOption.SO_KEEPALIVE, Boolean.TRUE)//默認不使用堆外内存.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)//false 不使用堆外内存.childOption(ChannelOption.ALLOCATOR, new UnpooledByteBufAllocator(false))// .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();//正常情况/*p.addLast("SampleInBoundHandlerA", new SampleInBoundHandler("SampleInBoundHandlerA", false));p.addLast("SampleInBoundHandlerB", new SampleInBoundHandler("SampleInBoundHandlerB", false));p.addLast("SampleInBoundHandlerC", new SampleInBoundHandler("SampleInBoundHandlerC", true));*/p.addLast("SampleInBoundHandlerA", new SampleExceptionInBoundHandler("SampleInBoundHandlerA", false));p.addLast("SampleInBoundHandlerB", new SampleExceptionInBoundHandler("SampleInBoundHandlerB", false));p.addLast("SampleInBoundHandlerC", new SampleExceptionInBoundHandler("SampleInBoundHandlerC", true));//添加异常处理器p.addLast(new ExceptionHandler());p.addLast("SampleOutBoundHandlerA", new SampleOutBoundHandler("SampleOutBoundHandlerA"));p.addLast("SampleOutBoundHandlerB", new SampleOutBoundHandler("SampleOutBoundHandlerB"));p.addLast("SampleOutBoundHandlerC", new SampleOutBoundHandler("SampleOutBoundHandlerC"));}});ChannelFuture f = b.bind(8090).sync();f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();}}
}
//进站出站异常处理器
public class ExceptionHandler extends ChannelDuplexHandler {@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {if (cause instanceof RuntimeException) {System.out.println("Handle Business Exception Success.");}}
}
``` 加入统一的异常处理器后,可以看到异常已经被优雅地拦截并处理掉了。这也是 Netty 推荐的最佳异常处理实践。
总结
本节课我们深入分析了 Pipeline 的设计原理与事件传播机制。
那么前面的几个问题你是否已经都找到答案,来做个简单的总结:
- ChannelPipeline 是双向链表结构,包含 ChannelInboundHandler 和 ChannelOutboundHandler 两种处理器。
- ChannelHandlerContext 是对 ChannelHandler 的封装,每个 ChannelHandler 都对应一个 ChannelHandlerContext,实际上 ChannelPipeline 维护的是与 ChannelHandlerContext 的关系。
- Inbound 事件和 Outbound 事件的传播方向相反,Inbound 事件的传播方向为 Head -> Tail,而 Outbound 事件传播方向是 Tail -> Head。
- 异常事件的处理顺序与 ChannelHandler 的添加顺序相同,会依次向后传播,与 Inbound 事件和 Outbound 事件无关。
相关文章:
![](https://img-blog.csdnimg.cn/img_convert/cc2de8fe7bfe240e1669331c88d03651.png?x-oss-process=image/watermark,image_bG9nby9jc2RuXzEucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLGhfMTIy,g_se,x_0,y_0,t_100)
16.Netty源码之ChannelPipeline
highlight: arduino-light 服务编排层:ChannelPipeline协调ChannelHandlerHandler EventLoop可以说是 Netty 的调度中心,负责监听多种事件类型:I/O 事件、信号事件、定时事件等,然而实际的业务处理逻辑则是由 ChannelPipeline 中所定义的 Cha…...
![](https://www.ngui.cc/images/no-images.jpg)
“使用Spring Boot构建微服务应用的最佳实践“
标题:使用Spring Boot构建微服务应用的最佳实践 摘要:本文将介绍如何使用Spring Boot构建微服务应用的最佳实践。我们将讨论微服务架构的概念、Spring Boot的优势以及一些最佳实践,同时提供示例代码帮助读者更好地理解和实践。 正文&#x…...
![](https://img-blog.csdnimg.cn/e337b60ed4934656aba5dab2f40d169c.png)
redis高可用之主从复制,哨兵,集群
目录 前言 一、主从复制 1、主从复制的作用 2、主从复制流程 3、部署Redis 主从复制步骤 3.1 环境准备 3.3 修改Redis 配置文件(Master节点操作) 3.4 修改Redis 配置文件(Slave节点操作) 3.5 验证主从效果 二、哨兵 1、哨兵模式原理 2、哨兵模式…...
![](https://img-blog.csdnimg.cn/4d4c3ebff298452b90b5d16e23b8d011.png)
【Ajax】笔记-原生jsonp跨域请求案例
原生jsonp跨域请求 输入框:输入后,鼠标移开向服务端发送请求,返回用户不存在(直接返回不存在,不做判断) JS <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><me…...
![](https://img-blog.csdnimg.cn/a27f4dc3d3d9402cabb54d532c2e74e7.png)
QT--day2(信号与槽,多界面跳转)
第一个界面头文件: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QIcon> //图标头文件 #include <QPushButton> //按钮类头文件QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public…...
![](https://img-blog.csdnimg.cn/img_convert/e10868be9dccda8266fdff82e0fa201e.png)
热备份路由协议原理
热备份路由协议原理 HSRP协议/VRRP协议热备份协议 热备份协议(Hot Standby Protocol) 是一种基于冗余设计的协议,用于提高网络的可靠性和冗余性。它允许多个设备共享同一个IP地址,其中一个设备被选为主设备,其他设备…...
![](https://www.ngui.cc/images/no-images.jpg)
模拟实现定时器
关于java标准库中的定时器的使用可以看定时器Timer的使用 大致思路 定义一个MyTimeTask类,该类用于组织要执行任务的内容以及执行任务的时间戳,后面要根据当前系统时间以及执行任务的时间戳进行比较,来判断是否要执行任务或是要等待任务 用一…...
![](https://www.ngui.cc/images/no-images.jpg)
TCP/IP的分包粘包
TCP/IP的分包粘包 分包粘包介绍导致分包粘包的原因导致TCP粘包的原因:导致TCP分包的原因:避免分包粘包的措施 分包粘包介绍 因为TCP为了减少额外开销,采取的是流式传输,所以接收端在一次接收的时候有可能一次接收多个包。而TCP粘…...
![](https://img-blog.csdnimg.cn/4960bd5766d544798921d149c4f93ced.jpeg)
盘点:查快递教程
在“寄快递”成为常态的当下,如何快速进行物流信息查询,是收寄人所关心的问题。在回答这个问题之前,首先我们要知道,物流信息查询,有哪些方法? 1、官网单号查询 知道物流公司和单号的情况下,直…...
![](https://oscimg.oschina.net/oscnet/up-a740e324e78757c2d01487caa238bbc104c.png)
TransGPT 开源交通大模型开源
TransGPT 是开源交通大模型,主要致力于在真实交通行业中发挥实际价值。 它能够实现交通情况预测、智能咨询助手、公共交通服务、交通规划设计、交通安全教育、协助管理、交通事故报告和分析、自动驾驶辅助系统等功能。 TransGPT 作为一个通用常识交通大模型&#…...
![](https://img-blog.csdnimg.cn/aec3b1a99a31457b94f9592bdf1b5b94.png)
gitignore文件使用方法(gitignore教程)(git status --ignored)(git check-ignore -v <file>)
文章目录 Gitignore文件使用描述Gitignore基本语法1. 基本语法★★★★★2. 配置方法 匹配示例示例1示例2示例3 其他命令git status --ignored(用于显示被Git忽略的文件和文件夹的状态)git check-ignore -v <file>(用于检查指定文件是否…...
![](https://www.ngui.cc/images/no-images.jpg)
mybatis拼接sql导致的oom报错 GC报错
报错1:mybatis拼接过多 java.lang.OutOfMemoryError: GC overhead limit exceeded 具体报错: nested exception is org.apache.ibatis.builder.BuilderException: Error evaluating expression ew.sqlSegment ! null and ew.sqlSegment ! and ew.non…...
![](https://img-blog.csdnimg.cn/img_convert/ee0834084aa36aab3169977c2c2af524.png)
如何通俗理解扩散模型?
扩散模型(Diffusion Model)是一类十分先进的基于扩散思想的深度学习生 成模型。生成模型除了扩散模型之外,还有出现较早的 VAE ( Variational Auto- Encoder,变分自编码器) 和 GAN ( Generative Adversarial Net ,生成对抗网络) 等。 虽然它们…...
【C#】并行编程实战:并行编程中的模式
本章将介绍并行编程模式,重点是理解并行代码问题场景并使用并行编程/异步技术解决他们。本章会介绍几种最重要的编程模式。 本教程学习工程:魔术师Dix / HandsOnParallelProgramming GitCode 1、MapReduce 模式 引入 MapReduce 是为了解决处理大数据的问…...
![](https://www.ngui.cc/images/no-images.jpg)
Apache Kafka 入门教程
Apache Kafka 入门教程 一、简介简介架构 二、Kafka 安装和配置JDK安装 Kafka配置文件详解 三、Kafka 的基本操作启动和关闭Topic 创建和删除Partitions 和 Replication 配置Producer 和 Consumer 使用方法ProducerConsumer 四、Kafka 高级应用消息的可靠性保证Kafka StreamKaf…...
![](https://img-blog.csdnimg.cn/img_convert/ec2e9b44f21100ea91ea7e48e7e51a07.jpeg)
python皮卡丘编程代码教程,用python打印皮卡丘
大家好,小编来为大家解答以下问题,如何用print函数打印一只皮卡丘,用python如何打印丘比特之心,现在让我们一起来看看吧!...
![](https://img-blog.csdnimg.cn/4c39daaac4e44a36985054b5532f00cb.png)
shell脚本:数据库的分库分表
#!/bin/bash ######################### #File name:db_fen.sh #Version:v1.0 #Email:admintest.com #Created time:2023-07-29 09:18:52 #Description: ########################## MySQL连接信息 db_user"root" db_password"RedHat123" db_cmd"-u${…...
![](https://www.ngui.cc/images/no-images.jpg)
AtCoder Beginner Contest 312(A~D)
A //语法题也要更仔细嘞,要不然也会wa #include <bits/stdc.h> // #pragma GCC optimize(3,"Ofast","inline") // #pragma GCC optimize(2) using namespace std; typedef long long LL; #define int LL typedef pair<int, int> …...
![](https://www.ngui.cc/images/no-images.jpg)
SQL中Partition的相关用法
使用Partition可以根据指定的列或表达式将数据分成多个分区。每个分区都是逻辑上独立的,可以单独进行查询、插入、更新和删除操作。Partition可以提高查询性能,因为它可以限制在特定分区上执行查询,而不是在整个表上执行。 在SQL中ÿ…...
![](https://img-blog.csdnimg.cn/f9b61e08c48b4d37a5fbf60c223d37db.png)
微服务——Docker
docker与虚拟机的区别 首先要知道三个层次 硬件层:计算机硬件 内核层:与硬件交互,提供操作硬件的指令 应用层: 系统应用封装内核指令为函数,便于程序员调用。用户程序基于系统函数库实现功能。 docker在打包的时候直接把应用层的函数库也进行打包&a…...
![](https://img-blog.csdnimg.cn/1e0b1a9ef07145b88efcab5987fe4529.png)
测试|测试用例方法篇
测试|测试用例方法篇 文章目录 测试|测试用例方法篇1.测试用例的基本要素:测试环境,操作步骤,测试数据,预期结果…2.测试用例带来的好处3.测试用例的设计思路,设计方法,具体设计方法之间的关系**设计测试用…...
![](https://www.ngui.cc/images/no-images.jpg)
负载均衡的策略有哪些? 负载均衡的三种方式?
负载均衡的策略有哪些? 负载均衡的策略有如下: 1. 轮询(Round Robin):按照请求的顺序轮流分配到不同的服务器。 2. 权重(Weighted):给不同的服务器分配不同的权重,根据权重比例来…...
![](https://img-blog.csdnimg.cn/ccd6d5b34caf4ead95b6e858d310a4f7.png)
二十三章:抗对抗性操纵的弱监督和半监督语义分割的属性解释
0.摘要 弱监督语义分割从分类器中生成像素级定位,但往往会限制其关注目标对象的一个小的区域。AdvCAM是一种图像的属性图,通过增加分类分数来进行操作。这种操作以反对抗的方式实现,沿着像素梯度的相反方向扰动图像。它迫使最初被认为不具有区…...
![](https://www.ngui.cc/images/no-images.jpg)
curator实现的zookeeper可重入锁
Curator是一个Apache开源的ZooKeeper客户端库,它提供了许多高级特性和工具类,用于简化在分布式环境中使用ZooKeeper的开发。其中之一就是可重入锁。 Curator提供了InterProcessMutex类来实现可重入锁。以下是使用Curator实现ZooKeeper可重入锁的示例&am…...
![](https://img-blog.csdnimg.cn/0e8ed9de41234cf6b41bf8cd986aeb22.png)
抽象工厂模式——产品族的创建
1、简介 1.1、简介 抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品 1.2、定义 抽象工厂模式(Abstract Factory Pattern):提供…...
![](https://img-blog.csdnimg.cn/2fa02dff54c24f75a68f419c86cf25b5.png#pic_center)
【C语言初阶篇】自定义类型结构体我不允许还有人不会!
🎬 鸽芷咕:个人主页 🔥 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想,就是为了理想的生活! 文章目录 📋 前言1 . 什么是结构体1.1 结构的定义1.2 结构的声明 2.结构体初始化2.1 用标签名定义和初始化2.2…...
![](https://img-blog.csdnimg.cn/29b8a24fd60645dbabdded646166cd9a.jpeg)
重大更新|Sui主网即将上线流动性质押,助力资产再流通
Sui社区一直提议官方上线流动质押功能,现在通过SIP过程,已经升级该协议以实现这一功能。 Sui使用委托权益证明机制(DPoS)来选择和奖励负责运营网络的验证节点。为了保障网络安全,验证节点通过质押SUI token获得质押奖…...
![](https://www.ngui.cc/images/no-images.jpg)
day3 驱动开发 c语言编程
通过ioctl(内核应用层) 控制led灯三盏,风扇,蜂鸣器,小马达 头文件head.h #ifndef __LED_H__ #define __LED_H__typedef struct {volatile unsigned int TZCR; // 0x000volatile unsigned int res1[2]; // 0x…...
![](https://img-blog.csdnimg.cn/9b0df72add7842fe97be4139aa2afd9a.png)
【字节跳动青训营】后端笔记整理-3 | Go语言工程实践之测试
**本文由博主本人整理自第六届字节跳动青训营(后端组),首发于稀土掘金:🔗Go语言工程实践之测试 | 青训营 目录 一、概述 1、回归测试 2、集成测试 3、单元测试 二、单元测试 1、流程 2、规则 3、单元测试的例…...
![](https://www.ngui.cc/images/no-images.jpg)
【Android】Recyclerview的缓存复用
介绍 RecyclerView是Android开发中常用的一个高度可定制的列表视图组件。它是在ListView和GridView的基础上进行了改进和增强,旨在提供更好的性能和更灵活的布局管理。 RecyclerView的主要特点如下: 灵活的布局管理器(LayoutManager&#…...
设计做网站/展示型网站有哪些
如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 下载了网易公开课的视频之后,发现它的中文和英文字母是分开的,用其他播放器打开的时候只能载入一个字幕,于是就想合并2个文件 思路…...
![](/images/no-images.jpg)
傻瓜式做网站程序/百度客服人工电话24小时
异步 HTTP 客户端Tornado 包含了两种非阻塞式 HTTP 客户端实现:SimpleAsyncHTTPClient 和 CurlAsyncHTTPClient。前者是直接基于 IOLoop 实现的,因此无需外部依赖关系。 后者作为 Curl 客户端,需要安装 libcurl 和 pycurl 后才能正常工作&…...
![](/images/no-images.jpg)
文山北京网站建设/网站优化培训学校
正则表达式(通用)目录文章目录1、概述1.1、序言1.2、作用2、正则字符3、元字符3.1、普通字符3.2、特殊字符:2.4、位置限定5、转义字符5.1、普通转义字符5.2、转义字符(范围字符)6、()、[]、{}作用7、量词7.1、数字量词7.2、符号量词7.3、懒惰…...
![](https://img-blog.csdnimg.cn/6f019b96e65d41bc855b1099f4c4ea18.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQmlsbDY2,size_13,color_FFFFFF,t_70,g_se,x_16)
怎样注册一个网站做电商/关键词歌曲
在写VC及标准C程序时,我们常用sprint函数来将数值转换为字符串。可在Keil C中使用这个函数时就要注意了。请看下面例程: /*main.cDesigned by Bill LiuVersion 0.0 Modified last by Bill Liu on 12/19/2021 */#include "main.h" ui8 tem; u…...
![](https://img-blog.csdnimg.cn/2019012622561734.png)
南阳移动端网站制作/最新app推广
k-means聚类算法原理简介概要算法思想算法流程图解代码实现算法优化代价函数(Distortion function)如何选取k值聚类中心的初始化概要 K-means算法是最普及的聚类算法,也是一个比较简单的聚类算法,所以刚接触的同学不要感到害怕。…...
![](/images/no-images.jpg)
二级域名可以做网站/网络营销怎么推广
微信 php 自定义菜单我申请了一个微信公众平台的测试账号,之前已经通过验证,关注后用我的微信号向测试账号发消息可以得到测试账号的正确响应,我现在想实现微信的自定义菜单,下面用***代替了我测试账号的appid和secret,…...