Pipeline 的事件传播机制

Pipeline 的事件传播机制

前面章节中,我们已经知道 AbstractChannelHandlerContext 中有 inbound 和 outbound 两个 boolean 变量,分别用于标识 Context 所对应的 handler 的类型,即:

1、inbound 为 true 是,表示其对应的 ChannelHandler 是 ChannelInboundHandler 的子类。

2、outbound 为 true 时,表示对应的 ChannelHandler 是 ChannelOutboundHandler 的子类。

这里大家肯定还有很多疑惑,不知道这两个字段到底有什么作用? 这还要从 ChannelPipeline 的事件传播类型说起。

Netty 中的传播事件可以分为两种:Inbound 事件和 Outbound 事件。如下是从 Netty 官网针对这两个事件的说明:

img

从上图可以看出,inbound 事件和 outbound 事件的流向是不一样的,inbound 事件的流行是从下至上,而 outbound刚好相反,是从上到下。并且 inbound 的传递方式是通过调用相应的 ChannelHandlerContext.fireIN_EVT()方法,而outbound 方法的的传递方式是通过调用 ChannelHandlerContext.OUT_EVT()方法。例如:ChannelHandlerContext 的 fireChannelRegistered()调用会发送一个 ChannelRegistered 的 inbound 给下一个 ChannelHandlerContext,而 ChannelHandlerContext 的 bind()方法调用时会发送一个 bind 的 outbound 事件给下一个 ChannelHandlerContext。

Inbound 事件传播方法有:

public interface ChannelInboundHandler extends ChannelHandler { 
    void channelRegistered(ChannelHandlerContext ctx) throws Exception; 
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception; 
    void channelActive(ChannelHandlerContext ctx) throws Exception; 
    void channelInactive(ChannelHandlerContext ctx) throws Exception; 
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception; 
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception; 
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception; 
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception; 
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; 
}

Outbound 事件传播方法有:

public interface ChannelOutboundHandler extends ChannelHandler { 

	void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception; 
	void connect( 
        ChannelHandlerContext ctx, SocketAddress remoteAddress, 
        SocketAddress localAddress, ChannelPromise promise) throws Exception; 
	void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; 
	void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; 
	void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; 
	void read(ChannelHandlerContext ctx) throws Exception; 
	void flush(ChannelHandlerContext ctx) throws Exception; 
}

大家应该发现了规律:inbound 类似于是事件回调(响应请求的事件),而 outbound 类似于主动触发(发起请求的 事件)。注意,如果我们捕获了一个事件,并且想让这个事件继续传递下去,那么需要调用 Context 对应的传播方法 fireXXX,例如:

public class MyInboundHandler extends ChannelInboundHandlerAdapter { 
@Override 
	public void channelActive(ChannelHandlerContext ctx) throws Exception { 
        System.out.println("连接成功"); 
        ctx.fireChannelActive(); 
	} 
}

public class MyOutboundHandler extends ChannelOutboundHandlerAdapter { 
@Override 
	public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception 		{ 
		System.out.println("客户端关闭"); 
		ctx.close(promise); 
	} 
}

如上面的示例代码:MyInboundHandler 收到了一个 channelActive 事件,它在处理后,如果希望将事件继续传播下去,那么需要接着调用 ctx.fireChannelActive()方法。

Outbound 事件传播方式

Outbound 事件都是请求事件(request event),即请求某件事情的发生,然后通过 Outbound 事件进行通知。

Outbound 事件的传播方向是 tail -> customContext -> head。

我们接下来以 connect 事件为例,分析一下 Outbound 事件的传播机制。

首先,当用户调用了 Bootstrap 的 connect()方法时,就会触发一个 Connect 请求事件,此调用会触发如下调用链:

img

继续跟踪,我们就发现 AbstractChannel 的 connect()其实由调用了 DefaultChannelPipeline 的 connect()方法:

public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { 
	return pipeline.connect(remoteAddress, promise); 
} 

而 pipeline.connect()方法的实现如下:

public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { 
	return tail.connect(remoteAddress, promise); 
} 

可以看到,当 outbound 事件(这里是 connect 事件)传递到 Pipeline 后,它其实是以 tail 为起点开始传播的。

而 tail.connect()其实调用的是 AbstractChannelHandlerContext 的 connect()方法:

public ChannelFuture connect( 
    final SocketAddress remoteAddress, 
    final SocketAddress localAddress, final ChannelPromise promise) { 
    //此处省略 N 句 
    final AbstractChannelHandlerContext next = findContextOutbound(); 
    EventExecutor executor = next.executor(); 
    next.invokeConnect(remoteAddress, localAddress, promise); 
    //此处省略 N 句 
    return promise; 
}

findContextOutbound()方法顾名思义,它的作用是以当前 Context 为起点,向 Pipeline 中的 Context 双向链表的前端 寻找第一个 outbound 属性为 true 的 Context(即关联 ChannelOutboundHandler 的 Context),然后返回。

findContextOutbound()方法代码实现如下:

private AbstractChannelHandlerContext findContextOutbound() { 
AbstractChannelHandlerContext ctx = this; 
do {
	ctx = ctx.prev; 
} while (!ctx.outbound); 
	return ctx; 
}

当我们找到了一个 outbound 的 Context 后,就调用它的 invokeConnect()方法,这个方法中会调用 Context 其关联的 ChannelHandler 的 connect()方法:

private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { 
	if (invokeHandler()) { 
        try {
    ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise); 
        } catch (Throwable t) { 
            notifyOutboundHandlerException(t, promise); 
        } 
        } else { 
            connect(remoteAddress, localAddress, promise); 
        } 
} 

如果用户没有重写 ChannelHandler 的 connect()方法,那么会调用 ChannelOutboundHandlerAdapter 的 connect() 实现:

public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, 
	SocketAddress localAddress, ChannelPromise promise) throws Exception { 
	ctx.connect(remoteAddress, localAddress, promise); 
} 

我们看到,ChannelOutboundHandlerAdapter 的 connect()仅仅调用了 ctx.connect(),而这个调用又回到了:

Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect

这样的循环中,直到 connect 事件传递到 DefaultChannelPipeline 的双向链表的头节点,即 head 中。为什么会传递 到 head 中呢?回想一下,head 实现了 ChannelOutboundHandler,因此它的 outbound 属性是 true。

因为 head 本身既是一个 ChannelHandlerContext,又实现了 ChannelOutboundHandler 接口,因此当 connect()消息 传递到 head 后,会将消息转递到对应的 ChannelHandler 中处理,而 head 的 handler()方法返回的就是 head 本身:

public ChannelHandler handler() { 
	return this; 
} 

因此最终 connect()事件是在 head 中被处理。head 的 connect()事件处理逻辑如下:

public void connect( 
    ChannelHandlerContext ctx, 
    SocketAddress remoteAddress, SocketAddress localAddress, 
    ChannelPromise promise) throws Exception { 
    unsafe.connect(remoteAddress, localAddress, promise); 
}

到这里, 整个 connect()请求事件就结束了。下图中描述了整个 connect()请求事件的处理过程:

img

我们仅仅以 connect()请求事件为例,分析了 outbound 事件的传播过程,但是其实所有的 outbound 的事件传播都遵循着一样的传播规律,小伙伴们可以试着分析一下其他的 outbound 事件,体会一下它们的传播过程。

### 魔搭 Pipeline 的流式处理实现方式 魔搭(ModelScope)是一个模型开放平台,提供了大量的机器学习和深度学习模型。对于 Pipeline 的流式处理实现方式,通常涉及以下几个核心部分: #### 1. **数据预处理** 在流式处理中,输入的数据通常是连续的或者分批次到达的。因此,在进入模型之前,需要对这些数据进行实时的预处理操作。这可能包括但不限于: - 数据清洗 - 特征提取 - 归一化/标准化 例如,如果使用的是自然语言处理任务中的 Transformer 模型,则可以采用如下代码完成 tokenization 和 padding[^2]。 ```python from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese") text = ["这是一个测试句子", "另一个例子"] tokens = tokenizer(text, padding=True, truncation=True, max_length=512, return_tensors="pt") print(tokens.input_ids.shape) # 输出形状应类似于 torch.Size([batch_size, seq_len]) ``` #### 2. **模型推理** 一旦数据被准备好,就可以将其送入到预先加载好的模型中进行推理。这里提到的 `embedding_delta` 计算实际上就是一种典型的前向传播过程的一部分[^1]。具体来说,通过矩阵乘法计算得到新的嵌入表示。 假设我们有一个已经训练好的线性层 \(w_{layer0}\),可以通过以下方式进行推断: ```python import torch # 假设 stacked_qkv_attention 是注意力机制后的输出张量 stacked_qkv_attention = torch.randn(17, 4096) # 定义权重矩阵 w_layer0 w_layer0 = torch.randn(4096, 4096) # 执行矩阵乘法获得 embedding delta embedding_delta = torch.matmul(stacked_qkv_attention, w_layer0.T) print(embedding_delta.shape) # 应该打印出 torch.Size([17, 4096]) ``` #### 3. **结果后处理** 最后一步是对模型输出的结果进行必要的后处理,以便于后续的应用程序能够理解并利用这些信息。比如分类任务可能会返回概率分布;而生成任务则会生成一段文字或其他形式的内容。 以下是基于上述流程的一个简单示例,展示如何构建一个完整的流式处理管道: ```python class StreamPipeline: def __init__(self, model_name_or_path): self.tokenizer = AutoTokenizer.from_pretrained(model_name_or_path) self.model = ... # 加载实际使用的模型 def preprocess(self, input_data): tokens = self.tokenizer(input_data, padding=True, truncation=True, max_length=512, return_tensors="pt") return tokens['input_ids'], tokens['attention_mask'] def inference(self, inputs): with torch.no_grad(): outputs = self.model(inputs) # 这里替换为具体的模型调用逻辑 return outputs.logits if hasattr(outputs, 'logits') else outputs def postprocess(self, logits): predictions = torch.argmax(logits, dim=-1).tolist() return predictions pipeline = StreamPipeline("bert-base-chinese") data = ["今天天气真好啊!", "明天会不会下雨呢?"] inputs = pipeline.preprocess(data) outputs = pipeline.inference(inputs) results = pipeline.postprocess(outputs) print(results) ``` 以上代码片段展示了从原始文本到最终预测标签的整体流水线设计思路。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值