面试题答案
一键面试线程模型角度
- 策略:
- 采用Netty默认的线程模型,如主从Reactor多线程模型。主Reactor线程负责接收客户端连接,从Reactor线程负责处理I/O读写等操作。在优雅停机时,要确保这些线程能够平稳停止,不会丢失正在处理的任务。
- 避免在停止过程中创建新的线程来处理任务,防止新任务加入未完成任务队列。
- 实现思路:
- 利用Netty的EventLoopGroup来管理线程。在停机时,调用
EventLoopGroup.shutdownGracefully()
方法。这个方法会逐步停止EventLoopGroup中的所有EventLoop线程。它会拒绝新的任务提交,并等待已提交的任务执行完毕,或等待达到设定的超时时间。例如:
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { // 服务器启动相关代码 } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }
- 利用Netty的EventLoopGroup来管理线程。在停机时,调用
资源释放角度
- 策略:
- 释放Netty使用的各种资源,包括但不限于Channel、ByteBuf等。确保在停机过程中没有资源泄漏,防止对后续系统启动或其他操作产生影响。
- 关闭与外部系统的连接,如数据库连接、缓存连接等,避免资源悬空占用。
- 实现思路:
- Channel资源释放:在关闭服务器时,遍历所有活动的Channel,并调用
channel.close()
方法。可以通过ChannelGroup
来管理所有的Channel,例如:
ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); ServerBootstrap b = new ServerBootstrap(); b.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { channelGroup.add(ch); // 其他Channel初始化操作 } }); // 服务器启动相关代码 // 停机时 channelGroup.close().sync();
- ByteBuf资源释放:Netty的ByteBuf有自动释放机制,但在一些复杂场景下,可能需要手动确保释放。例如,在自定义的编解码器中,如果持有ByteBuf对象,要在处理完成后及时调用
release()
方法释放资源。 - 外部连接关闭:对于数据库连接(如使用JDBC),在停机时关闭连接池。如果使用了连接池框架如HikariCP,可以调用
HikariConfig
的close()
方法关闭连接池。对于缓存连接(如Redis连接),使用相应客户端的关闭方法,如Jedis的jedis.close()
方法。
- Channel资源释放:在关闭服务器时,遍历所有活动的Channel,并调用
请求限流角度
- 策略:
- 在优雅停机过程中,限制新请求的进入,确保系统不会因为过多新请求而导致停机时间延长或出现数据丢失等问题。
- 合理处理已经在处理中的请求,保证这些请求能够正常完成处理。
- 实现思路:
- 令牌桶算法限流:可以使用Guava的
RateLimiter
实现令牌桶算法。在停机前设置一个较低的令牌发放速率,限制新请求的处理。例如:
RateLimiter rateLimiter = RateLimiter.create(1.0); // 每秒发放1个令牌 ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { if (!rateLimiter.tryAcquire()) { ch.close(); return; } // 正常的Channel初始化操作 } };
- 计数器限流:维护一个全局计数器,记录当前正在处理的请求数量。在停机前设置一个阈值,当请求到达时,如果计数器超过阈值则拒绝请求。可以使用AtomicInteger来实现线程安全的计数器,例如:
AtomicInteger requestCount = new AtomicInteger(0); ChannelInitializer<SocketChannel> initializer = new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { if (requestCount.incrementAndGet() > MAX_REQUEST_THRESHOLD) { requestCount.decrementAndGet(); ch.close(); return; } ch.closeFuture().addListener(future -> requestCount.decrementAndGet()); // 正常的Channel初始化操作 } };
- 处理已在途请求:对于已经在处理中的请求,可以通过设置一个标志位,如
isShuttingDown
。当该标志位为true
时,新请求被限流,而正在处理的请求继续处理完成。例如,在业务逻辑处理器中检查该标志位:
在停机时,先设置public class MyBusinessHandler extends ChannelInboundHandlerAdapter { private volatile boolean isShuttingDown; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (isShuttingDown) { // 可以选择关闭连接或者返回特定响应 ctx.close(); return; } // 正常业务处理逻辑 } }
isShuttingDown = true
,然后等待所有正在处理的请求完成(可以结合EventLoopGroup.shutdownGracefully()
的等待机制)。 - 令牌桶算法限流:可以使用Guava的