一、NIO模型简述
NIO (Non-blocking IO),称之为非阻塞IO,传输过程如下:
- 在NIO模式下,当用户进程执行系统调用后,如果当前数据还没有准备好,则会立即返回(NIO的非阻塞就提现在这里),然后再次进行系统调用,不断测试数据是否准备好。如果数据准备好了,当前进程会进入阻塞转态,直到数据从内核空间拷贝到用户空间,进程才会被唤醒,就可以处理数据了。
二、代码实现
1. 创建springBoot项目
目录结构:
2. 导入Maven依赖配置
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.53.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3. 创建UDP服务端并绑定端口
package com.demo.nettyDemo.udp;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
/**
* @title
* @date 2023/7/19 13:44
**/
public class NettyUdp implements Runnable {
// UDP服务端口
private static int PORT = 15001;
// 缓冲区默认最小值
private static int DEFAULT_MINIMUM = 512;
// 缓冲区默认初始值
private static int DEFAULT_INITIAL = 2048;
// 缓冲区默认最大值
private static int DEFAULT_MAXIMUM = 65536;
public static void main(String[] args) {
new NettyUdp().run();
}
@Override
public void run() {
// 创建Netty线程组,用于接收请求和处理IO请求
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
// 创建netty客户端启动器
Bootstrap b = new Bootstrap();
// 创建客户端与服务器的交互管道
Channel channel;
// 设置线程模型
b.group(eventLoopGroup)
// 指明网络通讯的是udp通讯
.channel(NioDatagramChannel.class)
// 设置netty接收缓冲区大小
.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(DEFAULT_MINIMUM,
DEFAULT_INITIAL, DEFAULT_MAXIMUM))
// 自定义处理接收的数据
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel nch) throws Exception {
// 在处理器链的最后添加一个NettyHandler
nch.pipeline().addLast(new NettyHandler());
}
});
try {
// 绑定端口
channel = b.bind(PORT).sync().channel();
// 阻塞主线程,等待服务端关闭
channel.closeFuture().sync().await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
4. 自定义数据包处理器
package com.demo.nettyDemo.udp;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
/**
* @title
* @date 2023/7/19 14:09
**/
@Slf4j(topic = "demoLog")
public class NettyHandler extends SimpleChannelInboundHandler<DatagramPacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
// 接收数据包的内容写入byte数组缓冲区
ByteBuf buf = msg.content();
// 读取byte数组缓冲区的长度
int len = buf.readableBytes();
// 创建字节数组,用于接收数据
byte[] data = new byte[len];
// 获取服务端的IP地址和端口号
InetSocketAddress inetSocketAddress = msg.sender();
// 将byte数组缓冲区的内容写入byte数组
buf.readBytes(data);
// 将字节数组转为string
String receive = new String(data, 0, len);
log.info(inetSocketAddress.getAddress().getHostAddress() + ":" + inetSocketAddress.getPort() + ":" + receive);
}
}
5. 在启动类中添加UDP服务启动线程
@SpringBootApplication
public class NettyDemoApplication {
public static void main(String[] args) {
SpringApplication.run(NettyDemoApplication.class, args);
// 开启线程启动UDP服务端
Thread udpThread = new Thread(new NettyUdp());
udpThread.start();
}
}
三、丢包率测试
- 使用Jmeter请求UDP端口,模拟高并发请求
- 测试情况:
- VMware:CentOS 7 2核4G
RPS | 测试时长(分钟) | 请求总量 | 丢包数 | 丢包率 |
---|---|---|---|---|
4000 | 5 | 1197850 | 82970 | 6.93% |
3000 | 5 | 895,538 | 25,375 | 2.83% |
2000 | 5 | 600,188 | 16,479 | 2.75% |
- 监控方法
- 修改内核中Sokcet接收缓冲区后测试
- VMware:CentOS 7 2核4G
- net.core.rmem_default = 1048576
- net.core.rmem_max = 1048576
QPS | 测试时长(分钟) | 请求总量 | 丢包数 | 丢包率 |
---|---|---|---|---|
4000 | 5 | 1,197,395 | 11,609 | 0.97% |
3000 | 5 | 89,5438 | 3,690 | 0.41% |
2000 | 5 | 600,201 | 0 | 0.00% |
四、结论
经过并发测试,简单优化调整下系统的socket接收缓冲区,效果依然没有达到预期,接下来将持续进行优化。