Netty学习笔记五
五. 优化与参数
1. 优化
1.1 扩展序列化算法
序列化,反序列化主要用在消息正文的转换上
- 序列化时,需要将 Java 对象变为要传输的数据(可以是 byte[],或 json 等,最终都需要变成 byte[])
- 反序列化时,需要将传入的正文数据还原成 Java 对象,便于处理
-
package com.sunyang.netty.study.protocol; import com.google.gson.*; import java.io.*; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; /** * 用于扩展序列化、反序列化算法 */ public interface Serializer { // 反序列化方法 // Class<T> clazz 反序列化时许需要把字节数组转化成哪个类型的对象 // JDK的反序列化是不需要类型信息的,因为JDK在序列化时也会把类信息放在字节数组里。但是用其他类型的序列化算法时,就必须指定要反序列化的类型。 <T> T deserialize(Class<T> clazz, byte[] bytes); // 序列化方法 <T> byte[] serialize(T object); enum Algorithm implements Serializer { Java { @Override public <T> T deserialize(Class<T> clazz, byte[] bytes) { try { ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); return (T) ois.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("反序列化失败", e); } } @Override public <T> byte[] serialize(T object) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(object); return bos.toByteArray(); } catch (IOException e) { throw new RuntimeException("序列化失败", e); } } }, Json { @Override public <T> T deserialize(Class<T> clazz, byte[] bytes) { String json = new String(bytes, StandardCharsets.UTF_8); return new Gson().fromJson(json, clazz); } @Override public <T> byte[] serialize(T object) { String json = new Gson().toJson(object); return json.getBytes(StandardCharsets.UTF_8); } } } }
1.2 参数调优
客户端通过
- .option()方法配置参数 给SocketChannel配置参数
服务端:
- .option() 方法是ServerSocketChannel配置参数
- .childOption() 是给SocketChannel配置参数。
1)CONNECT_TIMEOUT_MILLIS
-
属于 SocketChannal 参数
-
用在客户端建立连接时,如果在指定毫秒内无法连接,会抛出 timeout 异常
-
SO_TIMEOUT 主要用在阻塞 IO,阻塞 IO 中 accept,read 等都是无限等待的,如果不希望永远阻塞,使用它调整超时时间
@Slf4j
public class TestConnectionTimeout {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 300)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler());
ChannelFuture future = bootstrap.connect("127.0.0.1", 8080);
future.sync().channel().closeFuture().sync(); // 断点1
} catch (Exception e) {
e.printStackTrace();
log.debug("timeout");
} finally {
group.shutdownGracefully();
}
}
}
源码
目的:了解Netty中Promise和eventLoop的使用
连接超时的实现其实就是eventLoop的定时任务加上用Promise在主线程和eventLoop线程之间传递数据用。
io.netty.channel.nio.AbstractNioChannel.AbstractNioUnsafe#connect
@Override
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
// ...
// Schedule connect timeout.
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
ConnectTimeoutException cause =
new ConnectTimeoutException("connection timed out: " + remoteAddress); // 断点2
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}
// ...
}
2)SO_BACKLOG
- 属于 ServerSocketChannal 参数
-
第一次握手,client 发送 SYN 到 server,状态修改为 SYN_SEND,server 收到,状态改变为 SYN_REVD,并将该请求放入 sync queue 队列
-
第二次握手,server 回复 SYN + ACK 给 client,client 收到,状态改变为 ESTABLISHED,并发送 ACK 给 server
-
第三次握手,server 收到 ACK,状态改变为 ESTABLISHED,将该请求从 sync queue 放入 accept queue
-
服务器在三次握手连接建立以后并不能立刻直接拿到连接使用,而是从半连接队列放到全连接队列。
-
因为服务器端的accept()能力是有限的,所以要存在一个全连接队列,将不能被即时处理的连接信息存储在全连接队列中,等待accept()。
-
其中
-
在 linux 2.2 之前,backlog 大小包括了两个队列的大小,在 2.2 之后,分别用下面两个参数来控制
-
sync queue - 半连接队列
- 大小通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,在
syncookies
启用的情况下,逻辑上没有最大值限制,这个设置便被忽略
- 大小通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,在
-
accept queue - 全连接队列
- 其大小通过 /proc/sys/net/core/somaxconn 指定,在使用 listen 函数时,内核会根据传入的 backlog 参数与系统参数,取二者的较小值
- 如果 accpet queue 队列满了,server 将发送一个拒绝连接的错误信息到 client
NIO中
- 通过bind(8080, backlog);来设置大小
netty 中
- 可以通过 option(ChannelOption.SO_BACKLOG, 值) 来设置大小
- 默认值:Windows 200 Linux和MAC 128
3)ulimit -n
- 属于操作系统参数
- 限制一个进程能打开的最大文件描述符(FD)的数量
4)TCP_NODELAY
- 默认false ,开启了nagle算法
- 属于 SocketChannal 参数
- 建议设置为true,不延迟,直接发送。
5)SO_SNDBUF & SO_RCVBUF
- SO_SNDBUF 属于 SocketChannal 参数
- SO_RCVBUF 既可用于 SocketChannal 参数,也可以用于 ServerSocketChannal 参数(建议设置到 ServerSocketChannal 上)
6)ALLOCATOR
分配器
- 属于 SocketChannal 参数
- 用来分配 ByteBuf, ctx.alloc(),设置池化还是非池化,堆内存还是直接内存
- 默认是池化 pooled 直接内存
- 但是如果你设置了非直接内存,但是网络IO操作用的还是直接内存(Netty强制)但是自己创建的缓冲区(也就是用来本地IO操作的ByteBuf)可以设置。
7)RCVBUF_ALLOCATOR
- 属于 SocketChannal 参数
- 控制 netty 接收缓冲区大小
- 负责入站数据的分配,决定入站缓冲区的大小(并可动态调整),统一采用 direct 直接内存,具体池化还是非池化由 allocator 决定
的缓冲区(也就是用来本地IO操作的ByteBuf)可以设置。
7)RCVBUF_ALLOCATOR
- 属于 SocketChannal 参数
- 控制 netty 接收缓冲区大小
- 负责入站数据的分配,决定入站缓冲区的大小(并可动态调整),统一采用 direct 直接内存,具体池化还是非池化由 allocator 决定
[外链图片转存中…(img-U7AqEjVY-1629966016440)]