本文模拟三种宕机场景,分别演示实际测试效果和TCP调优实践。
程序准备
客户端
org.example.client.Main
package org.example.client;
import com.sun.corba.se.impl.ior.OldJIDLObjectKeyTemplate;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.System.out;
// 按两次 Shift 打开“随处搜索”对话框并输入 `show whitespaces`,
// 然后按 Enter 键。现在,您可以在代码中看到空格字符。
public class Main {
private static final ExecutorService worker = Executors.newSingleThreadExecutor();
private static AtomicInteger countConnection = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
int requests = Integer.parseInt(args[0]);
int connections = args.length > 1 ? Integer.parseInt(args[1]) : 1;
int threads = args.length > 2 ? Integer.parseInt(args[2]) : 1;
AtomicInteger count = new AtomicInteger(0);
CountDownLatch latch = new CountDownLatch(requests);
CountDownLatch latchConnection = new CountDownLatch(connections);
NioEventLoopGroup group = new NioEventLoopGroup(threads);
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new MyChannelInboundHandlerAdapter() {
@Override
public void channelRegistered(ChannelHandlerContext ctx)
throws Exception {
Object count = ctx.channel().attr(AttributeKey.valueOf("count")).get();
ctx.channel().attr(AttributeKey.valueOf("count")).set(count);
out.println( "第" +count + "个channelRegistered: channel注册到NioEventLoop");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx)
throws Exception {
Object count = ctx.channel().attr(AttributeKey.valueOf("count")).get();
out.println( "第" + count + "个channelUnregistered: channel取消和NioEventLoop的绑定");
latchConnection.countDown();
super.channelUnregistered(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx)
throws Exception {
Object count = ctx.channel().attr(AttributeKey.valueOf("count")).get();
out.println( ctx.channel() + ": channel准备就绪, 第"+ count +"个连接建立成功");
latchConnection.countDown();
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx)
throws Exception {
Object count =ctx.channel().attr(AttributeKey.valueOf("count")).get();
out.println( ctx.channel() + ": channel被关闭, 第" + count + "个连接关闭");
super.channelInactive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
Object count =ctx.channel().attr(AttributeKey.valueOf("count")).get();
out.println( "第" + count + "个channelRead: channel中有可读的数据");
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
Object count =ctx.channel().attr(AttributeKey.valueOf("count")).get();
out.println( "第" + count + "个channelReadComplete: channel读数据完成");
super.channelReadComplete(ctx);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx)
throws Exception {
int count = countConnection.incrementAndGet();
ctx.channel().attr(AttributeKey.valueOf("count")).set(count);
out.println("第" + count + "个handlerAdded: handler被添加到channel的pipeline");
super.handlerAdded(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx)
throws Exception {
Object count =ctx.channel().attr(AttributeKey.valueOf("count")).get();
out.println("第" + count + "个handlerRemoved: handler从channel的pipeline中移除");
super.handlerRemoved(ctx);
}
});
ChannelFuture[] channels = new ChannelFuture[connections];
long connectStart = System.currentTimeMillis();
for (int i = 0; i < connections; i++) {
// channels[i] = bootstrap.connect("192.168.253.148", 7070);
channels[i] = bootstrap.connect("192.168.254.196", 7070)
;
}
latchConnection.await();
long connectEnd = System.currentTimeMillis();
long start = System.currentTimeMillis();
// for (int i = 0; i < connections; i++) {
// if( count.incrementAndGet() <= requests ) {
// channels[i].writeAndFlush("request");
// }
// }
// latch.await();
long end = System.currentTimeMillis();
for (int i = 0; i < connections; i++) {
channels[i].channel().close()
.sync();
}
long closeEnd = System.currentTimeMillis();
group.shutdownGracefully();
Thread.sleep(2000);
// long delta = end - start;
// out.println("总计" + requests + "次请求, 耗时" + delta + "毫秒");
// out.println("QPS " + requests * 1000L / delta + "次/秒");
out.println( connections + "个连接建立, 耗时" + (connectEnd - connectStart) + "毫秒");
out.println("总计" + connections + "个连接, 耗时" + (closeEnd - connectStart) + "毫秒");
System.exit(0);
}
}
org.example.client.MyChannelInboundHandlerAdapter
package org.example.client;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
@ChannelHandler.Sharable
public class MyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter {
public MyChannelInboundHandlerAdapter()
{
System.out.println("MyChannelInboundHandlerAdapter created");
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>NettyTest</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.112.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 主动声明插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<!-- 绑定生命周期 -->
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<!-- 设置依赖的存放路径 -->
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
src/main/resources/logback.xml
<configuration debug="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="${logLevel:-info}">
<appender-ref ref="STDOUT" />
</root>
</configuration>
运行参数
java -Djava.net.preferIPv4Stack=true -Dio.netty.leakDetectionLevel=advanced -Xmx2048m -Xms2048m -cp NettyTest-1.0-SNAPSHOT.jar:lib/* org.example.client.Main 1 1
场景1 进程宕机
关键代码修改
客户端测试结果
tcpdump分析
场景2 局域网操作系统宕机
关键代码修改
客户端测试结果
tcpdump分析
场景3 广域网操作系统宕机
关键代码修改
客户端测试结果
tcp_syn_retries = 2