分布式微服务系统架构第118集:Future池管理容器-CompletableFuture

加群联系作者vx:xiaoda0423

仓库地址:https://webvueblog.github.io/JavaPlusDoc/

https://1024bat.cn/

阶段

说明

涉及数据结构

创建 bossGroup、workerGroup

分别管理接入连接和后续 IO 读写

NioEventLoopGroup

(内部有多线程执行)

配置 serverBootstrap

设置各种 Netty 参数,组装 ServerBootstrap

ServerBootstrap

注册 childHandler

xxServerChInit

 用于初始化每条新的连接 ChannelPipeline

ChannelInitializer<SocketChannel>

调用 bind(port)

把服务绑定到 9000 端口,监听 TCP 连接

ChannelFuture

客户端连接到服务器

bossGroup 接收连接,workerGroup 负责后续的数据通信

NioServerSocketChannel

NioSocketChannel

连接初始化

xxServerChInit

 中添加的 Handler 被执行

ChannelPipeline

(责任链模式)

读写数据处理

自定义 Handler 处理接收到的数据

自定义消息类(比如 JT808 报文、定制指令)

服务关闭

关闭所有资源,释放端口

shutdownGracefully

主要涉及的数据结构变化

变化点

变化描述

ServerBootstrap

 -> ChannelFuture

通过 bind 端口后,返回 ChannelFuture 表示异步绑定结果。

NioEventLoopGroup

 -> NioEventLoop

创建的 boss/worker 组内部是多个 NioEventLoop(线程 + Selector)。

SocketChannel

 -> ChannelPipeline

每次有连接建立时,初始化 ChannelPipeline,里面串联各种 Handler。

AdaptiveRecvByteBufAllocator

动态分配读缓冲区大小(避免内存浪费或过小导致频繁扩容)。

@Component
public class xxServerChInit extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 这里可以添加编解码器、业务处理器等
        pipeline.addLast(new FrameDecoder());   // 粘包拆包处理器
        pipeline.addLast(new ProtocolDecoder()); // 协议解码
        pipeline.addLast(new BusinessHandler()); // 具体业务逻辑
    }
}

这里的 pipeline 就像一条责任链,负责把客户端发过来的数据一层层加工和处理

阶段

说明

涉及类/数据结构

握手建立连接

Netty接收连接,调用 initChannel 方法

SocketChannel

创建责任链(Pipeline)

pipeline()

返回一个空白链表结构

DefaultChannelPipeline

加入 IdleStateHandler

监测180秒内是否有读写,否则触发 IdleStateEvent

IdleStateHandler

加入解码器 xxxDecoder

收到数据时,自动从 ByteBuf 解析成业务 POJO

xxDecoder

加入编码器 xxEncoder

发送数据时,把业务对象转成 ByteBuf

xxEncoder

加入业务处理器 xxxServerHandle

处理 decode后的业务消息,执行业务逻辑

xxServerHandle

主要涉及的数据结构变化

阶段

变化描述

SocketChannel.pipeline()

初始化一个新的责任链 DefaultChannelPipeline

addLast

 操作

往 Pipeline 中按顺序插入 Handler,形成链表

ByteBuf -> BusinessMessage

通过 Decoder 把原始字节流反序列化成对象

BusinessMessage -> ByteBuf

通过 Encoder 把业务对象编码成字节流

IdleStateEvent

超时后通过 pipeline 触发一个 userEventTriggered

JVM 调优:

1. 垃圾回收器选择

  • 建议:使用 G1 GC
    -XX:+UseG1GC

  • 特点:适合高并发、低延迟场景,GC暂停时间可控。

2. 堆内存配置

  • 根据实际连接数量和消息量,合理设置堆大小:

    -Xms2g -Xmx2g

    (初始和最大一致,避免动态扩容带来的卡顿)

3. Netty内存管理优化

  • Netty 默认用 PooledByteBufAllocator,确保启用池化:

    bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
  • 减少小对象频繁分配带来的GC压力。

4. 线程数配置

  • bossGroup 和 workerGroup 线程数可以根据 CPU 核心数调整:

    new NioEventLoopGroup(cpu核心数 * 2)
  • 例子:8核机器可以用 new NioEventLoopGroup(16)

5. 防止内存泄漏

  • Handler 一定要释放资源,比如 ByteBuf 的 release()。

  • 监控日志中是否有 Netty 报出的 LEAK: ByteBuf.release() 警告。

  • 保活(180s心跳超时检测)

  • 解码(字节 -> 业务对象)

  • 编码(业务对象 -> 字节)

  • 核心业务处理(执行业务逻辑)

阶段

主要处理

设备连接

channelActive:记录Channel

设备首次发消息

channelRead0解析

后续收消息

更新心跳、解析处理业务逻辑、回写结果

设备掉线

channelInactive清除映射关系

空闲超时

userEventTriggered主动关闭超时连接

基于 ReentrantLock + Condition

  • 支持异步线程之间的通知/等待。

  • 支持超时控制。

  • 手动 signal 触发返回结果。

  • 兼容 java.util.concurrent.Future 接口规范。

Condition适合这种低开销、少量同步的场景,比如一个Future小对象,不走重型同步器(像 CompletableFuture 里面是用 Unsafe)。

  • 存储一批等待异步完成的 CondFuture

  • 通过 id 来匹配具体的 Future。

  • 控制最大Future数量,防止内存爆掉。

  • 定期清理超时未完成的 Future。

  • 提供 setSignal(setSyncFuture)去异步唤醒 Future。

非常适合应用在 客户端请求异步等待场景,比如:

请求下发一个任务,客户端阻塞等待响应结果,超时自动清理。

  • 用 ConcurrentHashMap 保证多线程安全。

  • futureCount 作为保护阈值,避免堆积太多 Future。

  • invalidTime 检查超时的 Future,主动释放资源。

  • 支持动态唤醒(setSyncFuture)指定 Future。

  • 保持了轻量,没引入复杂调度任务,适合高性能环境。

项目

现状

改进

线程安全

基本ok

加强 remove/signal 一致性

类型安全

泛型丢失

用 T 泛型

并发性能

粗锁

double-check 减少锁

日志

频繁

只打异常日志

清理机制

嵌套

抽出 cleanExpiredFutures

补充:这个容器典型应用场景

  • Netty长连接服务器(客户端发起请求,需要服务器应答)

  • Kafka异步投递任务回调

  • RPC调用 Future 同步等待返回

  • IoT设备异步响应

  • 微服务之间的异步请求-响应

举个小例子,比如 Netty 收到一条业务请求,存个 CondFuture,异步发出去,下次 channelRead 回来,再通过 id 取出 Future,signal 一下返回响应,非常常见!

场景

说明

举例

Netty长连接服务器

请求-响应模式,需要在异步事件驱动下,临时保存请求上下文。

客户端发指令(比如充值请求),服务器先缓存 Future,异步处理完后 signal 返回结果。

Kafka异步投递回调

生产者发送消息后,不是立即确认,需要等待消费端处理回调。

Kafka 生产发出去后,异步监听回调 ack,等拿到ack再 signal Future。

RPC调用同步等待返回

微服务之间异步RPC调用,client端需要同步等待结果。

A系统请求B系统处理数据,不阻塞主线程,通过 Future await 等待异步回包。

IoT设备异步响应

大量设备与云端交互,请求和响应间隔时间不确定。

IoT终端发上传指令,云端收到保存 Future,等设备异步回执 signal。

微服务异步API网关

网关收到异步任务,需要同步返回响应,需要短暂缓存请求。

API网关请求分发到后端异步处理,Future await直到有结果,返回给前端。

延迟消息确认

某些系统需要发送消息后等待业务确认或补充处理。

下单后,发“支付成功消息”,等用户支付异步回调,signal Future并继续业务。

链路追踪跨服务调用

需要追踪一条链路的请求链,保持一个临时状态直到结束。

一个调用链起点保存 Future,链路回调后 signal 记录埋点结束。

(Netty里的超典型用法)

比如:

  1. 客户端发送一条请求,带 reqId=12345

  2. 服务器 Netty channelRead 收到请求后:

  • CondFutureContainer.getFuture("12345")

  • 保存一个 CondFuture

  • 继续异步向业务层派发请求

  • 业务处理需要异步,比如访问数据库、下游系统、或者Kafka投递

  • 后来异步事件返回,比如数据库处理好了/下游响应回来了

    • 业务代码调用 CondFutureContainer.setSyncFuture("12345", 响应内容)

    • 唤醒 Future,Netty 把响应写回客户端

  • 完成一次完整的异步请求-响应!

  • 示意伪代码:

    // 收到请求
    public void handleClientRequest(ChannelHandlerContext ctx, Request req) {
        CondFuture<Response> future = container.getFuture(req.getId());
        asyncBizProcess(req); // 异步处理
        Response response = future.await(30, TimeUnit.SECONDS);
        ctx.writeAndFlush(response); // 结果回来再响应客户端
    }
    
    // 异步处理完成后
    public void asyncBizCallback(String id, Response result) {
        container.setSyncFuture(id, result);
    }

    ⚡ 另外你提到 Kafka异步投递场景

    也可以,比如:

    • 生产者投递消息

    • 但 Kafka ack确认需要异步监听

    • 可以用 Future 容器 await

    • 监听器回调 signalFuture

    Kafka producer发送场景:

    // 发送消息
    public Response sendKafkaMsg(String id, Message msg) {
        CondFuture<Boolean> future = container.getFuture(id);
        kafkaTemplate.send("topic", msg);
        Boolean success = future.await(10, TimeUnit.SECONDS);
        return success ? Response.success() : Response.fail();
    }
    
    // Kafka 监听ack
    @KafkaListener(...)
    public void onKafkaAck(ProducerRecord record) {
        String id = record.key();
        container.setSyncFuture(id, true);
    }

    【使用 CondFuture 异步请求-响应】

    使用 CondFutureContainer 管理异步请求-响应的标准流程


    1. 客户端发送请求

    客户端(比如 Web、APP、IoT设备)发来一个请求,请求携带一个全局唯一的 ID(reqId)。

    2. 服务端 Netty 收到请求,准备 Future

    public void handleClientRequest(ChannelHandlerContext ctx, Request req) {
        // 2.1 从容器中根据请求ID拿到一个CondFuture(如果不存在就新建)
        CondFuture<Response> future = container.getFuture(req.getId());
    
        // 2.2 异步处理请求(比如下游数据库、Kafka、其他微服务)
        asyncBizProcess(req); 
    
        // 2.3 当前线程阻塞等待 future 被 signal,超时时间设定,比如30秒
        Response response = future.await(30, TimeUnit.SECONDS);
    
        // 2.4 Future被唤醒或者超时,发送响应回客户端
        ctx.writeAndFlush(response); 
    }

    3. 异步业务处理(比如发Kafka消息)

    public void asyncBizProcess(Request req) {
        // 发送消息到 Kafka,或者其他异步系统
        sendKafkaMsg(req.getId(), buildKafkaMessage(req));
    }

    4. 异步发送 Kafka 消息,继续挂 Future 等待回调

    public Response sendKafkaMsg(String id, Message msg) {
        // 再次确保有Future存在(可以视业务而定)
        CondFuture<Boolean> future = container.getFuture(id);
    
        // 异步发送 Kafka 消息
        kafkaTemplate.send("topic", msg);
    
        // 等待异步Kafka确认回调
        Boolean success = future.await(10, TimeUnit.SECONDS); // 10秒超时
        return success ? Response.success() : Response.fail();
    }

    5. Kafka异步回调,唤醒对应 Future

    @KafkaListener(topics = "topic")
    public void onKafkaAck(ProducerRecord record) {
        // 获取发送时设置的 ID
        String id = record.key();
        // 唤醒对应 Future,传递回调结果
        container.setSyncFuture(id, true);
    }

    6. 服务端继续处理(被 signal 唤醒)

    • await() 被 signal 成功唤醒

    • 取到回调的数据(比如 Response)

    • 通过 Netty ctx.writeAndFlush(response) 把最终响应写回客户端

    • 完成整个闭环

    🔥【完整时序图概览】

    客户端  ->  服务端Netty    ->  Future挂起
              服务端异步处理   ->  Kafka投递
              Kafka发送成功    ->  Kafka回调
              Kafka回调监听器 ->  signal Future
              Netty线程唤醒    ->  响应客户端

    🌟【一整套关键点总结】

    关键动作

    说明

    container.getFuture(id)

    新建或获取对应的 Future 实例

    future.await(timeout)

    阻塞等待异步结果,设置超时时间

    asyncBizProcess(req)

    异步处理逻辑,不要阻塞主线程

    container.setSyncFuture(id, result)

    异步回调后,唤醒对应 Future

    ctx.writeAndFlush(response)

    收到回调后,将结果响应给客户端

    超时处理

    如果超时,返回超时错误,保护服务线程不会死等

    容器清理机制

    定期清理过期的 Future,防止内存泄漏

    🛠️【注意点和最佳实践】

    • req.getId() 必须保证全局唯一,不要冲突!

    • future.await(timeout) 记得设置超时时间,防止永远挂死。

    • Kafka、MQ等异步系统,需要能正确拿到回调的 id

    • CondFutureContainer 要考虑过期数据清理(可以加定时器清理,保证健壮性)。

    • 容器最大数量(futureCount)可以根据业务并发量调整。

    • Signal的时候可以带业务处理结果对象(比如 Response 或者处理状态)。

    • 高性能

    • 可扩展

    • 异步超时可控

    • 防止内存泄露

    • 支持高并发请求 的 异步请求-响应框架了!

    而且可以随意套用在 Netty长连接Kafka消息投递IoT云端响应RPC调用 等各种场景!

    带超时保护和定时清理 Future的优化版 CondFutureContainer

    支持超时保护 + 定时清理过期 Future + 自动降级防爆内存

    📌 signal的作用

    在你这个 CondFuture 里面,signal(T object) 方法的意思是:

    ✅ 把【异步处理好的结果】塞进去(设置到object字段),
    ✅ 然后【唤醒】之前在 await() 方法里因为等待而挂起的线程。

    简单说:通知“兄弟,结果来了,醒醒,继续跑吧!”

    📦 源码回顾一下

    比如你的 CondFuture 类里,signal 是这样实现的:

    public void signal(T object) {
        this.object = object;
        lock.lock();
        try {
            condition.signal(); // 唤醒一个等待的线程
        } finally {
            lock.unlock();
        }
    }

    流程:

    1. this.object = object; 👉 先把结果保存。

    2. condition.signal(); 👉 再用 Condition 把await()里卡住的线程唤醒。

    3. 被唤醒的线程,就能拿到结果,继续干活了。

    🌟 举个小例子串起来理解

    1. Netty业务线程,收到客户端请求时:

    CondFuture<Response> future = container.getFuture(req.getId());
    Response resp = future.await(30, TimeUnit.SECONDS); // 这里阻塞等待

    它在await,等某人唤醒它。

    1. 异步处理线程,比如 Kafka 返回 ACK:

    container.setSyncFuture(req.getId(), response);

    内部调用了 future.signal(response),把结果塞进去并 唤醒

    1. 于是第1步里挂起的线程醒来,拿到resp,继续往客户端写回响应。

    🧠 核心理解一句话

    signal 就是把异步返回结果通知到同步等待的线程,打通整个流程的关键。

    🔥 总结口诀

    步骤

    动作

    线程1

    await()

     等待别人唤醒

    线程2

    业务处理完,signal(结果) 唤醒

    线程1

    醒了,拿到结果继续跑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值