使用Netty,我们到底在开发些什么?

在java界,netty无疑是开发网络应用的拿手菜。你不需要太多关注复杂的nio模型和底层网络的细节,使用其丰富的接口,可以很容易的实现复杂的通讯功能。

和golang的网络模块相比,netty还是太过臃肿。不过java类框架就是这样,属于那种离了IDE就无法存活的编码语言。

最新的netty版本将模块分的非常细,如果不清楚每个模块都有什么内容,直接使用netty-all即可。

单纯从使用方面来说,netty是非常简单的,掌握ByteBuf、Channel、Pipeline、Event模型等,就可以进行开发了。你会发现面试netty相关知识,没得聊。但Netty与其他开发模式很大不同,最主要的就是其异步化。异步化造成的后果就是编程模型的不同,同时有调试上的困难,对编码的要求比较高,因为bug的代价与业务代码的bug代价不可同日而语。

但从项目来说,麻雀虽小五脏俱全,从业务层到服务网关,以及各种技术保障,包括监控和配置,都是需要考虑的因素。netty本身占比很小。
在这里插入图片描述
本文将说明使用netty开发,都关注哪些通用的内容,然后附上单机支持100w连接的linux配置。本文并不关注netty的基础知识。

协议开发

网络开发中最重要的就是其通讯格式,协议。我们常见的protobuf、json、avro、mqtt等,都属于此列。协议有语法、语义、时序三个要素。

在这里插入图片描述

我见过很多中间件应用,采用的是redis协议,而后端落地的却是mysql;也见过更多的采用mysql协议实现的各种自定义存储系统,比如proxy端的分库分表中间件、tidb等。

我们常用的redis,使用的是文本协议;mysql等实现的是二进制协议。放在netty中也是一样,实现一套codec即可(继承Decoder或Encoder系列)。netty默认实现了dns、haproxy、http、http2、memcache、mqtt、redis、smtp、socks、stomp、xml等协议,可以说是很全了,直接拿来用很爽。

一个可能的产品结构会是这样的,对外提供一致的外观,核心存储却不同:

在这里插入图片描述
文本协议在调试起来是比较直观和容易的,但安全性欠佳;而二进制协议就需要依赖日志、wireshark等其他方式进行分析,增加了开发难度。传说中的粘包拆包,就在这里处理。而造成粘包的原因,主要是由于缓冲区的介入,所以需要约定双方的传输概要等信息,netty在一定程度上解决了这个问题。

每一个想要开发网络应用的同学,心里都埋了一颗重新设计协议的梦想种子。但协议的设计可以说是非常困难了,要深耕相应业务,还要考虑其扩展性。如没有特别的必要,建议使用现有的协议。

连接管理功能

做Netty开发,连接管理功能是非常重要的。通信质量、系统状态,以及一些黑科技功能,都是依赖连接管理功能。
在这里插入图片描述
无论是作为服务端还是客户端,netty在创建连接之后,都会得到一个叫做Channel的对象。我们所要做的,就是对它的管理,我习惯给它起名叫做ConnectionManager

管理类会通过缓存一些内存对象,用来统计运行中的数据。比如面向连接的功能:包发送、接收数量;包发送、接收速率;错误计数;连接重连次数;调用延迟;连接状态等。这会频繁用到java中concurrent包的相关类,往往也是bug集中地。

但我们还需要更多,管理类会给予每个连接更多的功能。比如,连接创建后,想要预热一些功能,那这些状态就可以参与路由的决策。通常情况下,将用户或其他元信息也attach到连接上,能够多维度的根据条件筛选一些连接,进行批量操作,比如灰度、过载保护等,是一个非常重要的功能。

管理后台可以看到每个连接的信息,筛选到一个或多个连接后,能够开启对这些连接的流量录制、信息监控、断点调试,你能体验到掌控一切的感觉。

管理功能还能够看到系统的整个运行状态,及时调整负载均衡策略;同时对扩容、缩容提供数据依据。

心跳检测

应用协议层的心跳是必须的,它和tcp keepalive是完全不同的概念。

应用层协议层的心跳检测的是连接双方的存活性,兼而连接质量,而keepalive检测的是连接本身的存活性。而且后者的超时时间默认过长,完全不能适应现代的网络环境。
在这里插入图片描述
心跳就是靠轮训,无论是服务端,还是客户端比如GCM等。保活机制会在不同的应用场景进行动态的切换,比如程序唤起和在后台,轮训的策略是不一样的。

Netty内置通过增加IdleStateHandler产生IDLE事件进行便捷的心跳控制。你要处理的,就是心跳超时的逻辑,比如延迟重连。但它的轮训时间是固定的,无法动态修改,高级功能需要自己定制。

在一些客户端比如Android,频繁心跳的唤起会浪费大量的网络和电量,它的心跳策略会更加复杂一些。

边界

优雅退出机制

Java的优雅停机通常通过注册JDK ShutdownHook来实现。

Runtime.getRuntime().addShutdownHook();

一般通过kill -15进行java进程的关闭,以便在进程死亡之前进行一些清理工作。

注意:kill -9 会立马杀死进程,不给遗言的机会,比较危险。

虽然netty做了很多优雅退出的工作,通过EventLoopGroupshutdownGracefully方法对nio进行了一些状态设置,但在很多情况下,这还不够多。它只负责单机环境的优雅关闭。

流量可能还会通过外层的路由持续进入,造成无效请求。我的通常做法是首先在外层路由进行一次本地实例的摘除,把流量截断,然后再进行netty本身的优雅关闭。这种设计非常简单,即使没有重试机制也会运行的很好,前提是在路由层需要提前暴露相关接口。
在这里插入图片描述

异常处理功能

netty由于其异步化的开发方式,以及其事件机制,在异常处理方面就显得异常重要。为了保证连接的高可靠性,许多异常需要静悄悄的忽略,或者在用户态没有感知。

netty的异常会通过pipeline进行传播,所以在任何一层进行处理都是可行的,但编程习惯上,习惯性抛到最外层集中处理。

为了最大限度的区别异常信息,通常会定义大量的异常类,不同的错误会抛出不同的异常。发生异常后,可以根据不同的类型选择断线重连(比如一些二进制协议的编解码紊乱问题),或者调度到其他节点。

功能限制

指令模式

网络应用就该干网络应用的事,任何通讯都是昂贵的。在《Linux之《荒岛余生》(五)网络篇》中,我们谈到百万连接的服务器,广播一个1kb消息,就需要1000M的带宽,所以并不是什么都可以放在网络应用里的。

一个大型网络应用的合理的思路就是值发送相关指令。客户端在收到指令以后,通过其他方式,比如http,进行大型文件到获取。很多IM的设计思路就是如此。

指令模式还会让通讯系统的扩展性和稳定性得到保证。增加指令可以是配置式的,立即生效,服务端不需要编码重启。

稳定性保证

网络应用的流量一般都是非常大的,并不适合全量日志的开启。应用应该只关注主要事件的日志,关注异常情况下的处理流程,日志要打印有度。

网络应用也不适合调用其他缓慢的api,或者任何阻塞I/O的接口。一些实时的事件,也不应该通过调用接口吐出数据,可以走高速mq等其他异步通道。

缓存可能是网络应用里用的最多的组件。jvm内缓存可以存储一些单机的统计数据,redis等存储一些全局性的统计和中间态数据。
在这里插入图片描述
网络应用中会大量使用redis、kv、高吞吐的mq,用来快速响应用户请求。总之,尽量保持通讯层的清爽,你会省去很多忧虑。

单机支持100万连接的Linux配置

单机支持100万连接是可行的,但带宽问题会成为显著的瓶颈。启用压缩的二进制协议会节省部分带宽,但开发难度增加。

《LWP进程资源耗尽,Resource temporarily unavailable》中提到的ES配置一样,优化都有类似的思路。这份配置,可以节省你几天的时间,请收下!

操作系统优化

更改进程最大文件句柄数

ulimit -n 1048576

修改单个进程可分配的最大文件数

echo 2097152 > /proc/sys/fs/nr_open

修改/etc/security/limits.conf文件

*   soft nofile  1048576
*   hard nofile 1048576
*   soft nproc unlimited
root soft nproc unlimited

记得清理掉/etc/security/limits.d/*下的配置

网络优化

打开/etc/sysctl.conf,添加配置
然后执行,使用sysctl生效

#单个进程可分配的最大文件数
fs.nr_open=2097152

#系统最大文件句柄数
fs.file-max = 1048576

#backlog 设置
net.core.somaxconn=32768
net.ipv4.tcp_max_syn_backlog=16384
net.core.netdev_max_backlog=16384

#可用知名端口范围配置
net.ipv4.ip_local_port_range='1000 65535'

#TCP Socket 读写 Buffer 设置
net.core.rmem_default=262144
net.core.wmem_default=262144
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.core.optmem_max=16777216
net.ipv4.tcp_rmem='1024 4096 16777216'
net.ipv4.tcp_wmem='1024 4096 16777216'

#TCP 连接追踪设置
net.nf_conntrack_max=1000000
net.netfilter.nf_conntrack_max=1000000
net.netfilter.nf_conntrack_tcp_timeout_time_wait=30

#TIME-WAIT Socket 最大数量、回收与重用设置
net.ipv4.tcp_max_tw_buckets=1048576

# FIN-WAIT-2 Socket 超时设置
net.ipv4.tcp_fin_timeout = 15

总结

netty的开发工作并不集中在netty本身,更多体现在保证服务的高可靠性和稳定性上。同时有大量的工作集中在监控和调试,减少bug修复的成本。

深入了解netty是在系统遇到疑难问题时能够深入挖掘进行排查,或者对苛刻的性能进行提升。但对于广大应用开发者来说,netty的上手成本小,死挖底层并不会产生太多收益。

它只是个工具,你还能让它怎样啊。

在这里插入图片描述

<think>好的,我现在需要回答用户关于Spring Boot在什么场景下可以使用Netty框架,以及使用Netty时需要注意什么的问题。首先,我需要回顾一下Spring Boot和Netty的基本知识,然后分析它们的结合点。 首先,Spring Boot是一个用于简化Spring应用开发的框架,提供了自动配置和快速启动的能力。它默认使用内嵌的Tomcat作为Web服务器,适用于大多数Web应用场景。而Netty是一个高性能的异步事件驱动的网络框架,常用于需要高并发、低延迟的网络应用,比如游戏服务器、即时通讯、实时数据传输等。 接下来,用户的问题分为两部分:适用场景和使用Netty时的注意事项。我需要先确定在Spring Boot项目中,哪些情况下适合引入Netty,然后总结使用Netty时需要注意的关键点。 关于适用场景,可能的例子包括需要处理大量长连接、高并发请求、自定义协议、或者需要更高效的资源利用率的情况。例如,实时聊天应用、物联网设备通信、高频交易系统等。在这些场景下,Netty的非阻塞IO模型和事件驱动机制比传统的Servlet容器(如Tomcat)更有优势。 然后是注意事项,这部分可能包括线程模型的理解、资源管理(如内存泄漏)、编解码器的设计、异常处理、与Spring生态的整合方式(比如如何将Netty服务整合到Spring的依赖注入中)、以及性能调优等方面。 在整理这些内容时,我需要确保涵盖以下几个方面: 1. **适用场景的具体例子**:给出几个典型的应用场景,说明为什么在这些情况下Netty比传统服务器更合适。 2. **性能需求分析**:比如高并发、低延迟、长连接管理等。 3. **协议处理**:自定义协议的支持,Netty的灵活性在这里的优势。 4. **与Spring Boot的整合方式**:比如通过自定义starter或者配置类来启动Netty服务器。 5. **注意事项的详细点**:比如线程模型、资源释放、内存管理、异常处理、依赖冲突等。 同时,需要注意用户可能已经了解了一些基础,但可能希望得到更深入的指导,尤其是在实际开发中容易遇到的问题和解决方案。例如,Netty的ByteBuf内存管理与Spring的Bean生命周期如何协调,或者如何避免常见的性能陷阱。 可能还需要对比Netty和传统的Servlet容器的区别,帮助用户更好地理解为何在某些情况下需要替换默认的Tomcat为Netty。例如,Servlet容器基于每个请求一个线程的模型,而Netty基于事件循环,更适合处理大量并发连接。 另外,关于注意事项,需要强调Netty的学习曲线较陡,尤其是对于不熟悉异步编程和事件驱动模型的开发者来说,可能需要更多的学习和调试时间。此外,Netty的版本兼容性、与Spring Boot的版本匹配也是需要注意的地方。 最后,要确保回答结构清晰,分点明确,便于用户理解和应用。可能需要使用示例代码或配置片段来说明如何整合Netty到Spring Boot项目中,以及常见问题的解决方法。</think>在Spring Boot项目中整合Netty框架,主要适用于需要高性能网络通信的场景。以下是具体场景及注意事项的详细说明: --- ### 一、适用场景 #### 1. **高并发长连接通信** - **典型场景**:即时通讯(IM)、在线游戏服务器、股票行情推送 - **需求特征**: - 同时维持10万+ TCP长连接 - 消息延迟要求<100ms - 要求低资源消耗(单机高连接密度) ```java // Netty服务端基础配置 public class NettyServer { EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 专门处理连接请求 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 默认线程数=CPU核心数*2 } ``` #### 2. **自定义协议处理** - **典型场景**:物联网设备接入(如Modbus、MQTT)、私有RPC协议 - **优势体现**: - 灵活实现协议编解码 - 支持二进制协议处理 - 可扩展的处理器链(pipeline) ```java // 自定义协议解码器示例 public class CustomDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { if (in.readableBytes() < HEADER_SIZE) return; // 解析协议头、校验数据完整性... } } ``` #### 3. **大文件传输优化** - **典型场景**:视频直播、P2P文件共享 - **关键技术**: - 零拷贝技术(FileRegion) - 分块传输 - 流量整形 ```java // 使用FileRegion实现零拷贝 FileRegion region = new DefaultFileRegion( file.getChannel(), 0, file.length()); ctx.writeAndFlush(region); ``` #### 4. **实时数据流处理** - **典型场景**:日志采集系统、金融交易数据流 - 实时统计分析 --- ### 二、整合方式 #### 1. 替换默认Web容器 ```xml <!-- 排除Tomcat依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!-- 添加Netty依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-reactor-netty</artifactId> </dependency> ``` #### 2. 独立服务模式 ```java @Configuration public class NettyConfig { @Bean(destroyMethod = "shutdown") public ServerBootstrap nettyBootstrap() { // 初始化Netty服务端 } } ``` --- ### 三、注意事项 #### 1. **线程模型优化** - **问题**:错误使用阻塞操作导致EventLoop阻塞 - **解决方案**: - I/O操作使用NIO线程(EventLoop) - 业务处理切换至业务线程池 ```java // 正确使用业务线程池 channelHandlerContext.executor().execute(() -> { // 立即执行任务 }); // 或使用独立的线程池 businessExecutor.submit(() -> { // 耗时业务处理 }); ``` #### 2. **内存管理** - **关键点**: - ByteBuf的引用计数管理 - 防止内存泄漏检测 - 使用内存池优化 ```java // 正确释放ByteBuf ByteBuf buf = Unpooled.buffer(128); try { // 使用缓冲区 } finally { buf.release(); // 显式释放 } ``` #### 3. **异常处理** - **必须实现**: - ChannelInboundHandlerAdapter.exceptionCaught() - 网络层异常日志记录 - 资源回收机制 ```java @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error("Channel异常: {}", ctx.channel().id(), cause); ctx.close(); // 立即关闭异常连接 } ``` #### 4. **与Spring整合** - **常见问题**: - Bean注入到ChannelHandler时的作用域问题 - Spring生命周期管理 - **解决方案**: ```java @Component @Scope("prototype") // 必须设置为原型模式 public class SpringAwareHandler extends ChannelInboundHandlerAdapter { @Autowired private BusinessService service; // 自动注入Spring Bean } ``` #### 5. **性能调优** - **关键参数**: | 参数项 | 推荐值 | 说明 | |---------|---------------------------|-------------------------------| | SO_BACKLOG | 1024 | 等待连接队列长度 | | WRITE_BUFFER_WATER_MARK | 32KB-64KB | 写缓冲区水位控制 | | ALLOCATOR | PooledByteBufAllocator | 启用内存池 | | TCP_NODELAY | true | 禁用Nagle算法 | --- ### 四、常见问题解决方案 #### 1. **CLOSE_WAIT堆积问题** - **现象**:大量连接处于CLOSE_WAIT状态 - **解决方法**: - 配置合理的空闲检测 ```java // 添加空闲检测handler pipeline.addLast(new IdleStateHandler(0, 0, 60)); pipeline.addLast(new HeartbeatHandler()); ``` #### 2. **消息粘包/拆包** - **解决方案**:组合使用解码器 ```java pipeline.addLast(new LengthFieldBasedFrameDecoder(1024*1024, 0, 4, 0, 4)); pipeline.addLast(new LengthFieldPrepender(4)); ``` #### 3. **性能压测工具** ```bash # 使用tcpcopy进行线上流量复制 ./tcpcopy -x 8080-10.0.0.1:8080 -s 10.0.0.2 -c 10.0.0.3 ``` --- ### 五、监控指标 | 监控项 | 正常范围 | 告警阈值 | |----------------------|---------------------|-------------------| | 当前活跃连接数 | < 80%最大承载量 | >90%最大承载量 | | 每秒处理消息量 | 根据业务定义 | 突降50%以上 | | 堆外内存使用率 | <60%物理内存 | >80%物理内存 | | EventLoop任务队列长度| <100 | >500 | 通过合理选择应用场景并遵循最佳实践,Spring Boot与Netty的结合可以构建出支持百万级并发的高性能网络服务。建议在复杂场景下配合使用Arthas进行运行时诊断,并建立完善的监控体系。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值