首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【2024博客之星】我的年度技术总结:Netty渡劫指南--从线程暴走到百万长连接,这一年我踩过的坑比写的代码还多

  • 25-02-18 12:21
  • 2795
  • 12904
blog.csdn.net

时间过得真快,作为一名十年的技术老鸟,这一年来跟Netty打交道打得不少。今天就聊聊这一年来我跟Netty的那些事儿,还有我在学习它技术原理时的一些总结。

导读

    • Netty再相见:捡起来、用起来
    • Netty原理学习:边啃边写变总结
    • Netty实战:干不爬我的终将被我干爬
      • 一、 为什么选择Netty?
      • 二、 线程模型:从车祸现场到秋名山车神
      • 三、 内存管理:从OOM拳皇到内存刺客
      • 四、 协议设计:从二进制乱码到量子通信
      • 五、 性能调优:从青铜到王者的九重天劫
      • 六、 填坑实录:那些让我掉头发的灵异事件
      • 七、 我们离完美还有多远?

Netty再相见:捡起来、用起来

记得刚开始接触Netty那会儿,大概10年前吧,技术很菜, 看的很浅,纯纯的是为了学习。但是呢, 10年前的东西早就还了回去, 好巧不巧, 今年接到一个技术重构项目需要用到netty,于是不得不重新开始学起来…当然这次要啃啃源码…

Netty这个异步事件驱动的网络应用框架,听起来挺高大上的,实际它就是帮我们封装好了Java NIO的那些底层细节,让我们能更专心地写业务逻辑,不用跟那些复杂的IO操作较劲。开始,看着Netty那一堆的API和组件,也比较乱。不过呢,Netty的文档还算齐全,社区也挺活跃的,一边查文档,一边看源码,慢慢地,也就上手了。

其实用了Netty一段时间之后,你会发现Netty的设计思路特别清晰,用起来也特别顺手。比如说,Netty的Pipeline和Handler机制。就像流水线和工人,每个Handler都可以对经过的事件进行处理或拦截。这种设计让Netty在处理网络事件时特别灵活,扩展性也强。

Netty原理学习:边啃边写变总结

当然要用好Netty,光知道怎么用可不行,还得知道它的技术原理。比如说,Netty的Reactor线程模型。Reactor模式是一种事件驱动的设计模式,特别适合处理并发IO操作。Netty通过实现多种Reactor模式,来适应不同的应用场景和需求。还有啊,Netty通过Direct Buffer、FileRegion等组件实现了零拷贝,从而大大提高了数据传输的性能。

边学源码, 边写写博客,当然写作质量我们另外考量…

netty源码解读:https://blog.csdn.net/qq_26664043/category_12729336.html

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


说了这么多,还是得拿实战来说话。


Netty实战:干不爬我的终将被我干爬

2024年的某个下午业务高峰期,监控大屏突然飙红——公司智慧物流平台的服务端像吃了泻药般疯狂Full GC。看着每秒10万+的物流轨迹数据在Kafka堆积成山,我握着保温杯的手微微颤抖:“Netty啊Netty,说好的’高性能异步框架’呢?” 这魔幻一幕,正是兄弟我与Netty年度缩影。


一、 为什么选择Netty?

年中接手物流中台项目时,大哥(leader)拉着我们聊聊技术:“单机支撑10万长连接,延迟不超过300ms”。那一刻仿佛听见了Spring WebFlux在角落里哭泣。

传统BIO的死亡现场:

  • 当模拟5000客户端压测时,Tomcat线程池直接罢工,日志里满是"RejectedExecutionException"
  • 同步阻塞模型下,每个请求都像在早高峰挤地铁——明明已经到站了,就是下不去车

Netty的三大绝活:

  1. 事件驱动模型:就像有个AI交警指挥交通,一个线程能处理N个路口(Channel)
  2. 零拷贝黑科技:FileRegion+CompositeByteBuf组合拳,内存复制开销直降70%
  3. 内存池化技术:ByteBuf的Arena分配策略,让JVM不再表演"内存过山车"

当第一个原型系统跑通时,资源监控显示:

# 传统BIO
Memory: 4G/8G (50%) 
Threads: 1500+(瑟瑟发抖)

# Netty
Memory: 1.2G/8G (15%)
Threads: 36(CPU核数x2) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

那一刻,大哥露出了"地主家有余粮"的微笑。


二、 线程模型:从车祸现场到秋名山车神

你以为用了Netty就能高枕无忧?Too young!第一次压测时,QPS刚到8000就触发了线程死锁,日志里堆栈信息直接把人干崩溃,程序员的崩溃…

经典翻车现场:

// 错误示范:在IO线程执行数据库操作
channel.pipeline().addLast(new SimpleChannelInboundHandler<ByteBuf>() {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
        // 在EventLoop线程执行JDBC查询 → 直接阻塞IO线程!
        userDao.query(msg.toString()); 
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

涅槃重生的线程架构:

+---------------------+
| BossGroup (NioEventLoop) 
| 处理Accept事件        |
+----------+----------+
           |
           v
+---------------------+
| WorkerGroup (NioEventLoop × N)
| 处理IO读写           |
+----------+----------+
           |
           v
+---------------------+
| BusinessThreadPool 
| 自定义业务线程池       | ← 这里才是处理慢操作的地方
+---------------------+
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

代码救赎之路:

// 使用额外的业务线程池
ExecutorService businessExecutor = Executors.newFixedThreadPool(32);

channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        businessExecutor.execute(() -> {
            // 把耗时操作扔到业务线程池
            processBusinessLogic(msg);
            ctx.writeAndFlush(response);
        });
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

调整后系统吞吐量直冲5万QPS,深藏功与名。


三、 内存管理:从OOM拳皇到内存刺客

某次生产环境半夜告警:“Heap usage超过90%”。打开MAT分析堆dump,发现DirectByteBuffer疯长——原来某个ChannelHandler忘记release ByteBuf了。

填坑四部曲:

  1. 开启内存泄露检测(代价是性能下降30%,仅调试时用)
// 启动参数添加
-Dio.netty.leakDetection.level=PARANOID
  • 1
  • 2
  1. 对象池化改造:
// 复用ByteBuf对象
private static final Recycler<ByteBuf> RECYCLER = new Recycler<>() {
    protected ByteBuf newObject(Handle<ByteBuf> handle) {
        return UnpooledByteBufAllocator.DEFAULT.buffer(1024).retain();
    }
};

ByteBuf buf = RECYCLER.get();
try {
    // 业务操作...
} finally {
    buf.release(); // 放回池中
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  1. 堆外内存限流:
// 防止DirectMemory耗尽
-Dio.netty.maxDirectMemory=2g
  • 1
  • 2
  1. 精准狙击内存泄漏:
// 继承SimpleChannelInboundHandler自动释放
public class MyHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
        // 无需手动release
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

改造后内存波动曲线从"过山车"变成了"老(年)太极"。


四、 协议设计:从二进制乱码到量子通信

通信协议文档写着:“第3字节为状态位,0x01代表正常”。结果真实数据里该位置突然出现0x03,有兄弟幽幽地说:“哦,我们上周刚加了’振动异常’状态…”

协议层防崩溃设计:

  1. 魔数校验(快速过滤无效连接)
// 协议头校验
if (msg.getInt(0) != 0xDEADBEEF) {
    ctx.close();
    return;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 动态协议适配
// 根据版本号选择解码器
int version = msg.getByte(4);
switch (version) {
    case 1 -> pipeline.addLast(new V1Decoder());
    case 2 -> pipeline.addLast(new V2Decoder());
    default -> throw new UnsupportedProtocolException();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 柔性降级策略:
// 使用Protobuf的扩展字段
message BasePacket {
    required int32 type = 1;
    extensions 1000 to max; // 为未来字段留空间
}
  • 1
  • 2
  • 3
  • 4
  • 5

这套机制成功扛住了3次协议变更。为什么频繁变更,懂的你都懂…


五、 性能调优:从青铜到王者的九重天劫

当技术总监要求"百万连接不卡顿"时,我知道真正的战斗开始了。以下是血泪换来的调优圣经:

调优参数表:

参数默认值优化值效果
SO_BACKLOG102432768半连接队列扩容
TCP_NODELAYfalsetrue禁用Nagle算法降延迟
SO_RCVBUF/SO_SNDBUF系统默认1MB避免小包频繁传输
ALLOCATORPooledUnpooled根据场景选择(高并发用池化)
WRITE_BUFFER_WATER_MARK32KB-64KB1MB-2MB大流量防写阻塞

JVM黄金搭档:

-server 
-Xmx4g -Xms4g 
-XX:+UseG1GC 
-XX:MaxGCPauseMillis=100 
-XX:InitiatingHeapOccupancyPercent=35
  • 1
  • 2
  • 3
  • 4
  • 5

Linux内核黑魔法:

# 调整最大文件描述符
ulimit -n 1000000

# 优化TCP参数
sysctl -w net.core.somaxconn=32768
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fin_timeout=15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

经过这番操作,单机长连接从5万飙到十几万,GC时间从800ms/次降到60ms。


六、 填坑实录:那些让我掉头发的灵异事件

  1. EPOLL空轮询BUG
    现象:CPU突然100%且持续不退
    解法:升级Netty到4.1.68+,或设置-Dio.netty.noKeySetOptimization=true

  2. ChannelHandler的暗黑生命周期
    掉坑:在handlerAdded()里写业务逻辑导致死锁
    忠告:记住Handler的调用链是"先add后init",别乱搞状态

  3. WriteAndFlush的量子纠缠
    经典错误:

    ctx.write(buffer1);
    ctx.write(buffer2);
    ctx.flush(); // 这里flush的只有buffer1!
    
    • 1
    • 2
    • 3

    正确姿势:用ChannelFuture future = ctx.write(buffer1).write(buffer2).flush();

  4. 空闲检测的狼来了
    误判设备离线?原来是心跳间隔设置不合理:

    // 服务端设置读空闲60秒
    pipeline.addLast(new IdleStateHandler(60, 0, 0)); 
    // 客户端设置写空闲30秒
    pipeline.addLast(new IdleStateHandler(0, 30, 0)); 
    
    • 1
    • 2
    • 3
    • 4

七、 我们离完美还有多远?

站在2025年的起点,Netty生态又有了新变化:

  • GraalVM原生镜像:启动时间从3秒缩短到0.3秒
  • QUIC协议支持:HTTP/3的UDP传输层已进入试验阶段
  • AI智能调控:基于LSTM预测流量波峰,动态调整线程池
  • 混沌工程防护:自动模拟网络抖动、包乱序等极端场景

回望这一年的技术长征,Netty就像一把杀猪刀——初见平平无奇,深究方知精妙。正如Netty之父Trustin Lee所说:“The performance is not an accident, it’s a design.” 或许这就是技术的魅力:用精心设计的架构,在比特洪流中搭建起秩序的方舟。

码到三十五
微信公众号
分享正经的开发技术(源码,原理,架构)
注:本文转载自blog.csdn.net的码到三十五的文章"https://blog.csdn.net/qq_26664043/article/details/145270072"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top