Netty-自定义线程池处理长耗时业务
为什么需要自定义线程处理
在Netty中,EventLoop 和 EventExecutorGroup 提供的普通任务不能解决长耗时业务处理引起的 I/O 线程阻塞问题,究其原因:在 Netty 中,一个 channel 在它的生命中期内只注册于一个 EventLoop,而一个 EventLoop 在它的生命周期内只和一个线程绑定,一个给定的 channel 的 I/O 操作都是由相同的线程进行处理。为此不能直接在该线程上进行长耗时业务处理。为了解决这个问题,通常的做法是:自定义线程池,把长耗时的业务都丢到线程池中去处理。
Guava 自定义线程池处理业务
基本实现思路如下:
- 引入 Guava 依赖,Guava是谷歌推出的基于开源的 Java 库,是谷歌很多项目的核心库,该库是为了增强 Java
的功能和处理能力,我们利用它来实现我们的业务线程池; - 在 Handler 中定义业务线程池,我们利用 Guava 创建线程池,这里面包含了线程启动的方式,线程池必要参数的设置,注意这里一定要设置线程为守护线程而不是用户线程,要不然麻烦多多,守护线程和用户线程的主要区别:主线程结束后用户线程还会继续运行,守护线程则会自动结束;
- 耗时业务放入线程池:用法相当简单:service.submit (我们要执行的业务处理),和我们前面讲到的普通任务和定时任务的放置完全一样。
添加依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
编码实现
服务端实现
package org.skystep.tcpserv;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup wokerGroup = new NioEventLoopGroup();
try {
//用于启动NIO服务
ServerBootstrap serverBootstrap = new ServerBootstrap();
//通过工厂方法设计模式实例化一个channel
serverBootstrap.group(bossGroup, wokerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ServerInitializer());
//绑定服务器,该实例将提供有关IO操作的结果或状态的信息
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
System.out.println("在" + channelFuture.channel().localAddress() + "上开启监听");
//阻塞操作,closeFuture()开启了一个channel的监听器(这期间channel在进行各项工作),直到链路断开
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
bossGroup.shutdownGracefully();
wokerGroup.shutdownGracefully();
}
}
}
package org.skystep.tcpserv;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new ServerHandler());
}
}
package org.skystep.tcpserv;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.concurrent.*;
@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {
//创建自定义业务线程池,用于非阻塞处理长耗时业务
protected static ExecutorService newFixedThreadPool() {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("netty-business-%d")
.setDaemon(true)//默认为false用户线程,这里设置为true守护线程。注意区别:主线程结束后用户线程还会继续运行,守护线程则会自动结束
.build();
return new ThreadPoolExecutor(
50,//核心线程数
200,//线程池中的能拥有的最多线程数
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10000), threadFactory);//10000表示用于缓存任务的阻塞队列,其实理解为最大并发量
}
final static ListeningExecutorService service = MoreExecutors.listeningDecorator(newFixedThreadPool());
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//读取消息,并按照自定义的协议格式进行消息的处理
service.submit(new Runnable() {
@Override
public void run() {
String in = (String) msg;
System.out.println("线程:" + Thread.currentThread().getName() + "收到的原始报文: " + in);
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//出现异常的时候执行的动作(打印并关闭通道)
cause.printStackTrace();
ctx.close();
}
}
客户端实现
package org.skystep.tcpclient;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ClientInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
package org.skystep.tcpclient;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public class ClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new ClientHandler());
}
}
package org.skystep.tcpclient;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.time.LocalDateTime;
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//服务端的远程地址
System.out.println(ctx.channel().remoteAddress());
System.out.println("client output: " + msg);
ctx.writeAndFlush("from client: " + LocalDateTime.now());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 在这里循环发送 1234567890,查看服务器接收数据结果
while (true) {
ctx.writeAndFlush("123467890");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}