feat: add Netty and Java NIO interview questions
Added 3 comprehensive interview documents covering Netty and Java NIO: **1. Netty Core Principles (Netty核心原理.md)** - Core components: Channel, EventLoop, ChannelPipeline, ByteBuf - Reactor threading model (single/multi-threaded, master-slave) - ByteBuf vs Java NIO ByteBuffer - Zero-copy implementation (4 approaches) - ChannelPipeline and ChannelHandler - TCP sticky/unpacking problem solutions - Heartbeat mechanism **2. Java NIO Core Principles (Java NIO核心原理.md)** - NIO vs BIO comparison - Three core components: Channel, Buffer, Selector - Selector multiplexing mechanism - Channel vs Stream differences - Buffer core attributes and usage - Non-blocking I/O implementation - Zero-copy with transferTo and MappedByteBuffer **3. Netty Practice Scenarios (Netty实战场景.md)** - High-performance RPC framework design - WebSocket server implementation - Million-connection IM system architecture - Memory leak detection and resolution - Graceful shutdown implementation - Heartbeat and reconnection mechanisms Each document includes: - Detailed problem descriptions - Complete code examples (Java) - Architecture diagrams - Best practices - Performance optimization tips - P7-level bonus points Total: 3 documents, covering: - Theoretical foundations - Practical implementations - Production scenarios - Performance tuning - Common pitfalls Suitable for backend P7 interview preparation. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
803
10-中间件/Netty实战场景.md
Normal file
803
10-中间件/Netty实战场景.md
Normal file
@@ -0,0 +1,803 @@
|
||||
# Netty 实战场景与最佳实践
|
||||
|
||||
## 问题
|
||||
|
||||
1. 如何设计一个高性能的 RPC 框架?
|
||||
2. 如何实现 WebSocket 服务器?
|
||||
3. 如何实现百万连接的 IM 系统?
|
||||
4. 如何处理 Netty 的内存泄漏?
|
||||
5. 如何实现优雅关闭?
|
||||
6. 如何设计心跳和重连机制?
|
||||
7. 如何实现分布式 Session 共享?
|
||||
|
||||
---
|
||||
|
||||
## 标准答案
|
||||
|
||||
### 1. 高性能 RPC 框架设计
|
||||
|
||||
#### **架构设计**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ RPC 框架架构 │
|
||||
├──────────────────────────────────────────┤
|
||||
│ Consumer Side │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Proxy │ │ Serializer│ │
|
||||
│ │ (动态代理)│ │ (Protobuf) │ │
|
||||
│ └────┬─────┘ └──────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ Netty Client │ │
|
||||
│ │ - Reconnect │ │
|
||||
│ │ - Timeout │ │
|
||||
│ │ - Heartbeat │ │
|
||||
│ └──────────────┬──────────────────┘ │
|
||||
│ │ │
|
||||
│ │ TCP │
|
||||
│ │ │
|
||||
│ ┌──────────────▼──────────────────┐ │
|
||||
│ │ Netty Server │ │
|
||||
│ │ - Boss/Worker Groups │ │
|
||||
│ │ - Pipeline │ │
|
||||
│ │ - Handler Chain │ │
|
||||
│ └──────────────┬──────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────┴─────────┐ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────┐ ┌──────────┐ │
|
||||
│ │Service │ │ Registry │ │
|
||||
│ │(实现) │ │ (服务发现)│ │
|
||||
│ └─────────┘ └──────────┘ │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### **协议设计**
|
||||
|
||||
```java
|
||||
// RPC 协议格式
|
||||
┌───────────┬───────────┬──────────┬──────────┐
|
||||
│ Magic │ Length │ Type │ Body │
|
||||
│ Number │ (4 bytes) │ (1 byte) │ │
|
||||
│ (4 bytes) │ │ │ │
|
||||
└───────────┴───────────┴──────────┴──────────┘
|
||||
|
||||
// 实现
|
||||
public class RpcProtocol {
|
||||
|
||||
private static final int MAGIC_NUMBER = 0xCAFEBABE;
|
||||
private static final int HEADER_LENGTH = 9;
|
||||
|
||||
public static ByteBuf encode(byte[] body, byte type) {
|
||||
ByteBuf buffer = Unpooled.buffer(HEADER_LENGTH + body.length);
|
||||
|
||||
// Magic Number (4 bytes)
|
||||
buffer.writeInt(MAGIC_NUMBER);
|
||||
|
||||
// Length (4 bytes)
|
||||
buffer.writeInt(body.length);
|
||||
|
||||
// Type (1 byte)
|
||||
buffer.writeByte(type);
|
||||
|
||||
// Body
|
||||
buffer.writeBytes(body);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static byte[] decode(ByteBuf buffer) {
|
||||
// 检查 Magic Number
|
||||
if (buffer.readInt() != MAGIC_NUMBER) {
|
||||
throw new IllegalArgumentException("Invalid magic number");
|
||||
}
|
||||
|
||||
// 读取 Length
|
||||
int length = buffer.readInt();
|
||||
|
||||
// 读取 Type
|
||||
byte type = buffer.readByte();
|
||||
|
||||
// 读取 Body
|
||||
byte[] body = new byte[length];
|
||||
buffer.readBytes(body);
|
||||
|
||||
return body;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **服务端实现**
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class RpcServer {
|
||||
|
||||
private EventLoopGroup bossGroup;
|
||||
private EventLoopGroup workerGroup;
|
||||
private Channel serverChannel;
|
||||
|
||||
public void start(int port) throws InterruptedException {
|
||||
bossGroup = new NioEventLoopGroup(1);
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
|
||||
try {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.option(ChannelOption.SO_BACKLOG, 1024)
|
||||
.option(ChannelOption.SO_REUSEADDR, true)
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
// 编解码器
|
||||
pipeline.addLast("frameDecoder",
|
||||
new LengthFieldBasedFrameDecoder(8192, 4, 4, 0, 0));
|
||||
pipeline.addLast("frameEncoder",
|
||||
new LengthFieldPrepender(4));
|
||||
|
||||
// 协议编解码
|
||||
pipeline.addLast("decoder", new RpcDecoder());
|
||||
pipeline.addLast("encoder", new RpcEncoder());
|
||||
|
||||
// 心跳检测
|
||||
pipeline.addLast("idleStateHandler",
|
||||
new IdleStateHandler(60, 0, 0));
|
||||
pipeline.addLast("heartbeatHandler", new HeartbeatHandler());
|
||||
|
||||
// 业务处理器
|
||||
pipeline.addLast("handler", new RpcServerHandler());
|
||||
}
|
||||
});
|
||||
|
||||
// 绑定端口并启动
|
||||
ChannelFuture future = bootstrap.bind(port).sync();
|
||||
serverChannel = future.channel();
|
||||
|
||||
System.out.println("RPC Server started on port " + port);
|
||||
|
||||
// 等待服务器 socket 关闭
|
||||
serverChannel.closeFuture().sync();
|
||||
|
||||
} finally {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (serverChannel != null) {
|
||||
serverChannel.close();
|
||||
}
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
if (bossGroup != null) {
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **客户端实现**
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class RpcClient {
|
||||
|
||||
private EventLoopGroup workerGroup;
|
||||
private Channel channel;
|
||||
|
||||
public void connect(String host, int port) throws InterruptedException {
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
|
||||
try {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(workerGroup)
|
||||
.channel(NioSocketChannel.class)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
// 编解码器
|
||||
pipeline.addLast("frameDecoder",
|
||||
new LengthFieldBasedFrameDecoder(8192, 4, 4, 0, 0));
|
||||
pipeline.addLast("frameEncoder",
|
||||
new LengthFieldPrepender(4));
|
||||
|
||||
// 协议编解码
|
||||
pipeline.addLast("decoder", new RpcDecoder());
|
||||
pipeline.addLast("encoder", new RpcEncoder());
|
||||
|
||||
// 心跳检测
|
||||
pipeline.addLast("idleStateHandler",
|
||||
new IdleStateHandler(0, 30, 0));
|
||||
pipeline.addLast("heartbeatHandler", new HeartbeatHandler());
|
||||
|
||||
// 业务处理器
|
||||
pipeline.addLast("handler", new RpcClientHandler());
|
||||
}
|
||||
});
|
||||
|
||||
// 连接服务器
|
||||
ChannelFuture future = bootstrap.connect(host, port).sync();
|
||||
channel = future.channel();
|
||||
|
||||
System.out.println("RPC Client connected to " + host + ":" + port);
|
||||
|
||||
// 等待连接关闭
|
||||
channel.closeFuture().sync();
|
||||
|
||||
} finally {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T sendRequest(RpcRequest request, Class<T> returnType) {
|
||||
// 发送请求并等待响应
|
||||
RpcClientHandler handler = channel.pipeline()
|
||||
.get(RpcClientHandler.class);
|
||||
|
||||
return handler.sendRequest(request, returnType);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. WebSocket 服务器实现
|
||||
|
||||
#### **WebSocket 握手机制**
|
||||
|
||||
```
|
||||
Client → Server: HTTP GET (Upgrade: websocket)
|
||||
Server → Client: HTTP 101 (Switching Protocols)
|
||||
Client → Server: WebSocket Frame
|
||||
Server → Client: WebSocket Frame
|
||||
```
|
||||
|
||||
#### **服务器实现**
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class WebSocketServer {
|
||||
|
||||
private EventLoopGroup bossGroup;
|
||||
private EventLoopGroup workerGroup;
|
||||
|
||||
public void start(int port) throws InterruptedException {
|
||||
bossGroup = new NioEventLoopGroup(1);
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
|
||||
try {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
// HTTP 编解码器
|
||||
pipeline.addLast("httpCodec", new HttpServerCodec());
|
||||
pipeline.addLast("httpAggregator",
|
||||
new HttpObjectAggregator(65536));
|
||||
|
||||
// WebSocket 握手处理器
|
||||
pipeline.addLast("handshakeHandler",
|
||||
new WebSocketServerProtocolHandler("/ws"));
|
||||
|
||||
// 自定义 WebSocket 处理器
|
||||
pipeline.addLast("handler", new WebSocketHandler());
|
||||
}
|
||||
});
|
||||
|
||||
ChannelFuture future = bootstrap.bind(port).sync();
|
||||
System.out.println("WebSocket Server started on port " + port);
|
||||
|
||||
future.channel().closeFuture().sync();
|
||||
|
||||
} finally {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
if (bossGroup != null) {
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **WebSocket Handler**
|
||||
|
||||
```java
|
||||
@ChannelHandler.Sharable
|
||||
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||
|
||||
// 存储所有连接的 Channel
|
||||
private static final ChannelGroup channels =
|
||||
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
channels.add(ctx.channel());
|
||||
System.out.println("Client connected: " + ctx.channel().id());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
channels.remove(ctx.channel());
|
||||
System.out.println("Client disconnected: " + ctx.channel().id());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx,
|
||||
TextWebSocketFrame msg) {
|
||||
// 接收消息
|
||||
String message = msg.text();
|
||||
System.out.println("Received: " + message);
|
||||
|
||||
// 广播消息给所有客户端
|
||||
channels.writeAndFlush(new TextWebSocketFrame(
|
||||
"Server: " + message
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 百万连接 IM 系统
|
||||
|
||||
#### **架构优化**
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class ImServerConfig {
|
||||
|
||||
@Bean
|
||||
public EventLoopGroup bossGroup() {
|
||||
// 1 个线程处理 Accept
|
||||
return new NioEventLoopGroup(1);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EventLoopGroup workerGroup() {
|
||||
// 根据 CPU 核心数设置 I/O 线程数
|
||||
// 通常设置为 CPU 核心数 * 2
|
||||
int threads = Runtime.getRuntime().availableProcessors() * 2;
|
||||
return new NioEventLoopGroup(threads);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerBootstrap serverBootstrap(
|
||||
EventLoopGroup bossGroup,
|
||||
EventLoopGroup workerGroup) {
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.option(ChannelOption.SO_BACKLOG, 8192) // 增大连接队列
|
||||
.option(ChannelOption.SO_REUSEADDR, true)
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
.childOption(ChannelOption.SO_SNDBUF, 32 * 1024) // 32KB 发送缓冲
|
||||
.childOption(ChannelOption.SO_RCVBUF, 32 * 1024) // 32KB 接收缓冲
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
// 连接数限制
|
||||
pipeline.addLast("connectionLimiter",
|
||||
new ConnectionLimiter(1000000));
|
||||
|
||||
// 流量整形
|
||||
pipeline.addLast("trafficShaping",
|
||||
new ChannelTrafficShapingHandler(
|
||||
1024 * 1024, // 读限制 1MB/s
|
||||
1024 * 1024 // 写限制 1MB/s
|
||||
));
|
||||
|
||||
// 协议编解码
|
||||
pipeline.addLast("frameDecoder",
|
||||
new LengthFieldBasedFrameDecoder(8192, 0, 4, 0, 0));
|
||||
pipeline.addLast("frameEncoder",
|
||||
new LengthFieldPrepender(4));
|
||||
|
||||
// IM 协议处理器
|
||||
pipeline.addLast("imHandler", new ImServerHandler());
|
||||
}
|
||||
});
|
||||
|
||||
return bootstrap;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **连接限流**
|
||||
|
||||
```java
|
||||
public class ConnectionLimiter extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final int maxConnections;
|
||||
private final AtomicInteger activeConnections = new AtomicInteger(0);
|
||||
|
||||
public ConnectionLimiter(int maxConnections) {
|
||||
this.maxConnections = maxConnections;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (activeConnections.get() >= maxConnections) {
|
||||
// 连接数超限,关闭新连接
|
||||
ctx.writeAndFlush(new TextWebSocketFrame(
|
||||
"Server is full, please try again later"
|
||||
)).addListener(ChannelFutureListener.CLOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = activeConnections.incrementAndGet();
|
||||
System.out.println("Active connections: " + count);
|
||||
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
int count = activeConnections.decrementAndGet();
|
||||
System.out.println("Active connections: " + count);
|
||||
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 内存泄漏排查与解决
|
||||
|
||||
#### **常见泄漏场景**
|
||||
|
||||
```java
|
||||
// ❌ 错误示例 1: ByteBuf 未释放
|
||||
public void handler(ChannelHandlerContext ctx, ByteBuf buf) {
|
||||
byte[] data = new byte[buf.readableBytes()];
|
||||
buf.readBytes(data);
|
||||
// 忘记释放 buf
|
||||
}
|
||||
|
||||
// ✅ 正确做法
|
||||
public void handler(ChannelHandlerContext ctx, ByteBuf buf) {
|
||||
try {
|
||||
byte[] data = new byte[buf.readableBytes()];
|
||||
buf.readBytes(data);
|
||||
// 处理数据...
|
||||
} finally {
|
||||
ReferenceCountUtil.release(buf); // 释放 ByteBuf
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 错误示例 2: Handler 未移除
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
// 添加长生命周期的 Handler
|
||||
ctx.pipeline().addLast("longLived", new LongLivedHandler());
|
||||
}
|
||||
|
||||
// ✅ 正确做法
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
// 使用 @Sharable 注解
|
||||
ctx.pipeline().addLast("longLived", SharableHandler.INSTANCE);
|
||||
}
|
||||
|
||||
// ❌ 错误示例 3: Channel 引用未释放
|
||||
public class ChannelHolder {
|
||||
private static final Map<String, Channel> CHANNELS = new ConcurrentHashMap<>();
|
||||
|
||||
public static void addChannel(String id, Channel channel) {
|
||||
CHANNELS.put(id, channel); // 永久持有引用
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 正确做法
|
||||
public class ChannelHolder {
|
||||
private static final Map<String, Channel> CHANNELS = new ConcurrentHashMap<>();
|
||||
|
||||
public static void addChannel(String id, Channel channel) {
|
||||
CHANNELS.put(id, channel);
|
||||
|
||||
// 监听关闭事件,自动移除
|
||||
channel.closeFuture().addListener((ChannelFutureListener) future -> {
|
||||
CHANNELS.remove(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **内存泄漏检测**
|
||||
|
||||
```java
|
||||
// 启用内存泄漏检测
|
||||
// JVM 参数: -Dio.netty.leakDetection.level=paranoid
|
||||
|
||||
public class LeakDetection {
|
||||
public static void main(String[] args) {
|
||||
// 检测级别
|
||||
// DISABLED - 禁用
|
||||
// SIMPLE - 采样检测(1%)
|
||||
// ADVANCED - 采样检测,记录创建堆栈
|
||||
// PARANOID - 全量检测
|
||||
|
||||
// 创建资源时使用
|
||||
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(1024);
|
||||
|
||||
// 使用资源泄漏检测器访问器
|
||||
ResourceLeakDetector<ByteBuf> detector =
|
||||
new ResourceLeakDetector<ByteBuf>(ByteBuf.class,
|
||||
ResourceLeakDetector.Level.PARANOID);
|
||||
|
||||
// 包装对象
|
||||
ByteBuf wrappedBuffer = detector.wrap(buffer, "test-buffer");
|
||||
|
||||
// 使用完成后
|
||||
wrappedBuffer.release(); // 如果未释放,会记录堆栈并警告
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 优雅关闭实现
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class NettyGracefulShutdown implements SmartLifecycle {
|
||||
|
||||
@Autowired
|
||||
private ServerBootstrap serverBootstrap;
|
||||
|
||||
private Channel serverChannel;
|
||||
private volatile boolean running = false;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
running = true;
|
||||
// 启动服务器...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Shutting down Netty server gracefully...");
|
||||
|
||||
// 1. 停止接受新连接
|
||||
if (serverChannel != null) {
|
||||
serverChannel.close().syncUninterruptibly();
|
||||
}
|
||||
|
||||
// 2. 等待现有请求处理完成(超时 30 秒)
|
||||
EventLoopGroup workerGroup = serverBootstrap.config().group();
|
||||
Future<?> shutdownFuture = workerGroup.shutdownGracefully(2, 30, TimeUnit.SECONDS);
|
||||
|
||||
try {
|
||||
// 等待优雅关闭完成
|
||||
shutdownFuture.await(60, TimeUnit.SECONDS);
|
||||
|
||||
if (shutdownFuture.isSuccess()) {
|
||||
System.out.println("Netty server shutdown successfully");
|
||||
} else {
|
||||
System.err.println("Netty server shutdown timeout, force close");
|
||||
workerGroup.shutdownNow();
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
workerGroup.shutdownNow();
|
||||
}
|
||||
|
||||
// 3. 关闭 Boss Group
|
||||
EventLoopGroup bossGroup = serverBootstrap.config().childGroup();
|
||||
bossGroup.shutdownGracefully();
|
||||
|
||||
running = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. 心跳与重连机制
|
||||
|
||||
#### **心跳处理器**
|
||||
|
||||
```java
|
||||
@ChannelHandler.Sharable
|
||||
public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final ByteBuf HEARTBEAT_BEAT =
|
||||
Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HEARTBEAT", CharsetUtil.UTF_8));
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent) evt;
|
||||
|
||||
if (event.state() == IdleState.READER_IDLE) {
|
||||
// 读空闲,可能对方已挂掉
|
||||
System.out.println("Read idle, closing connection");
|
||||
ctx.close();
|
||||
|
||||
} else if (event.state() == IdleState.WRITER_IDLE) {
|
||||
// 写空闲,发送心跳
|
||||
System.out.println("Write idle, sending heartbeat");
|
||||
ctx.writeAndFlush(HEARTBEAT_BEAT.duplicate());
|
||||
|
||||
} else if (event.state() == IdleState.ALL_IDLE) {
|
||||
// 读写空闲
|
||||
System.out.println("All idle, sending heartbeat");
|
||||
ctx.writeAndFlush(HEARTBEAT_BEAT.duplicate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
// 处理心跳响应
|
||||
if ("HEARTBEAT".equals(msg)) {
|
||||
// 心跳包,不处理
|
||||
return;
|
||||
}
|
||||
|
||||
// 业务消息
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **重连机制**
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class ReconnectClient {
|
||||
|
||||
private EventLoopGroup workerGroup;
|
||||
private Channel channel;
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
public void connect(String host, int port) {
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
doConnect(host, port);
|
||||
}
|
||||
|
||||
private void doConnect(String host, int port) {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(workerGroup)
|
||||
.channel(NioSocketChannel.class)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ch.pipeline()
|
||||
.addLast("idleStateHandler",
|
||||
new IdleStateHandler(0, 10, 0))
|
||||
.addLast("heartbeatHandler", new HeartbeatHandler())
|
||||
.addLast("handler", new ClientHandler());
|
||||
}
|
||||
});
|
||||
|
||||
// 连接服务器
|
||||
ChannelFuture future = bootstrap.connect(host, port);
|
||||
|
||||
future.addListener((ChannelFutureListener) f -> {
|
||||
if (f.isSuccess()) {
|
||||
System.out.println("Connected to server");
|
||||
channel = f.channel();
|
||||
} else {
|
||||
System.out.println("Connection failed, reconnect in 5 seconds...");
|
||||
|
||||
// 5 秒后重连
|
||||
scheduler.schedule(() -> doConnect(host, port), 5, TimeUnit.SECONDS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P7 加分项
|
||||
|
||||
### 深度理解
|
||||
- **Reactor 模式**:理解主从 Reactor 多线程模型
|
||||
- **零拷贝原理**:理解用户空间和内核空间
|
||||
- **内存管理**:ByteBuf 的池化、引用计数、泄漏检测
|
||||
|
||||
### 实战经验
|
||||
- **高并发调优**:EventLoopGroup 线程数、TCP 参数调优
|
||||
- **监控告警**:连接数监控、流量监控、延迟监控
|
||||
- **故障排查**:内存泄漏、CPU 100%、连接泄漏
|
||||
|
||||
### 架构设计
|
||||
- **协议设计**:自定义协议、编解码器设计
|
||||
- **集群部署**:负载均衡、服务发现
|
||||
- **容错机制**:重连、熔断、限流
|
||||
|
||||
### 性能优化
|
||||
1. **减少内存拷贝**:使用 CompositeByteBuf
|
||||
2. **复用对象**:使用 ByteBuf 对象池
|
||||
3. **优化 TCP 参数**:TCP_NODELAY、SO_KEEPALIVE
|
||||
4. **流量控制**:ChannelTrafficShapingHandler
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
Netty 实战核心要点:
|
||||
1. **高性能**:Reactor 模型、零拷贝、内存池
|
||||
2. **高可靠**:心跳、重连、优雅关闭
|
||||
3. **可扩展**:Pipeline 机制、自定义协议
|
||||
4. **可监控**:连接数、流量、延迟监控
|
||||
|
||||
**最佳实践**:
|
||||
- 合理设置 EventLoopGroup 线程数
|
||||
- 使用池化的 ByteBuf,及时释放
|
||||
- 添加心跳和重连机制
|
||||
- 实现优雅关闭
|
||||
- 做好监控和告警
|
||||
- 定期排查内存泄漏
|
||||
Reference in New Issue
Block a user