Netty-自定义线程池处理长耗时业务

为什么需要自定义线程处理

在Netty中,EventLoop 和 EventExecutorGroup 提供的普通任务不能解决长耗时业务处理引起的 I/O 线程阻塞问题,究其原因:在 Netty 中,一个 channel 在它的生命中期内只注册于一个 EventLoop,而一个 EventLoop 在它的生命周期内只和一个线程绑定,一个给定的 channel 的 I/O 操作都是由相同的线程进行处理。为此不能直接在该线程上进行长耗时业务处理。为了解决这个问题,通常的做法是:自定义线程池,把长耗时的业务都丢到线程池中去处理。

Guava 自定义线程池处理业务

基本实现思路如下:

  1. 引入 Guava 依赖,Guava是谷歌推出的基于开源的 Java 库,是谷歌很多项目的核心库,该库是为了增强 Java
    的功能和处理能力,我们利用它来实现我们的业务线程池;
  2. 在 Handler 中定义业务线程池,我们利用 Guava 创建线程池,这里面包含了线程启动的方式,线程池必要参数的设置,注意这里一定要设置线程为守护线程而不是用户线程,要不然麻烦多多,守护线程和用户线程的主要区别:主线程结束后用户线程还会继续运行,守护线程则会自动结束;
  3. 耗时业务放入线程池:用法相当简单: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();
    }
}

posted @ 2022-01-05 23:24  yaomianwei  阅读(1058)  评论(0编辑  收藏  举报