如何在netty的handler中开启异步操作

278 阅读3分钟

1. 分析故障:服务线程数过多,导致su - java进程用户 失败

1.1 故障原因

服务都是长连接,当我们有个长连接,我们会在这个会话当中再次开启一个异步线程来处理数据,这个异步线程是while(条件){queue.take()}

当我们的链接由于idlehandler导致四次挥手断开,这个时候handler的 channelUnregistered 会被触发,在这个里面我们要做的就是去关闭这个异步线程,条件=false,跳出while循环,线程就执行结束了

故障是因为while的判断条件出现饥饿了,queue.take()被阻塞了,while的条件得不到执行了

1.2 分析

如何正确关闭一个线程: 让线程执行完 不停地检测到他的打断标识,不停地去检测就要求循环体不能有阻塞的地方

禁止在while循环里面去阻塞

我们把异步线程设置成守护线程,也不能关闭的,要注意区分通道和线程的关系,通道关闭,但是线程还在

3. 解答本文的问题

讲述了上面的分析,开启异步操作总体而言两种方式

3.1 new Thread(), 但是在通道关闭的时候要关闭线程,防止出现上面分析的问题

3.2 内部利用ctx的通道的evenetLoop来执行任务,测试demo如下

/**
 * 1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范)
 * 2. 这时我们自定义一个Handler , 才能称为一个handler
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址
     * 2. Object msg: 就是客户端发送的数据 默认Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        /**
         * 比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交该channel 对应的
         * NIOEventLoop 的 taskQueue中,
         * 解决方案1 用户程序自定义的普通任务
         */
        /*ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    //Thread.sleep(Integer.MAX_VALUE);
                    System.out.println("检测tcp断掉了");
                    ctx.writeAndFlush(Unpooled.copiedBuffer("丁亚武测试异步2", CharsetUtil.UTF_8));
                    System.out.println("channel code=" + ctx.channel().hashCode());
                } catch (Exception ex) {
                    System.out.println("发生异常" + ex.getMessage());
                }
            }
        });*/
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(Integer.MAX_VALUE);
                System.out.println("channel code=" + ctx.channel().hashCode() + "检测tcp断掉了");
                ctx.writeAndFlush(Unpooled.copiedBuffer("丁亚武测试异步2", CharsetUtil.UTF_8));
            }catch (Exception e){

            }
        }, "test_thread");
        thread.setDaemon(true);
        thread.start();

        /*ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵3", CharsetUtil.UTF_8));
                    System.out.println("channel code=" + ctx.channel().hashCode());
                } catch (Exception ex) {
                    System.out.println("发生异常" + ex.getMessage());
                }
            }
        });*/

        /**
         * 解决方案2 : 用户自定义定时任务 -》 该任务是提交到 scheduleTaskQueue中
         */
       /* ctx.channel().eventLoop().schedule(new Runnable() {
            @Override
            public void run() {

                try {
                    Thread.sleep(5 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵4", CharsetUtil.UTF_8));
                    System.out.println("channel code=" + ctx.channel().hashCode());
                } catch (Exception ex) {
                    System.out.println("发生异常" + ex.getMessage());
                }
            }
        }, 5, TimeUnit.SECONDS);*/

        System.out.println("go on ...");
        System.out.println("服务器读取线程 " + Thread.currentThread().getName() + " channle =" + ctx.channel());
        System.out.println("server ctx =" + ctx);
        System.out.println("看看channel 和 pipeline的关系");
        Channel channel = ctx.channel();

        //将 msg 转成一个 ByteBuf
        //ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:" + channel.remoteAddress());
    }

    /**
     * 数据读取完毕
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        /**
         * writeAndFlush 是 write + flush
         * 将数据写入到缓存,并刷新
         * 一般讲,我们对这个发送的数据进行编码
         */
        ctx.writeAndFlush(Unpooled.copiedBuffer("丁亚武测试", CharsetUtil.UTF_8));
    }


    /**
     * 处理异常, 一般是需要关闭通道
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exceptionCaught检测到了异常,关闭通道");
        ctx.close();
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelRegistered检测到了连接,注册通道");
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelUnregistered检测到了异常,通道移除");
        super.channelUnregistered(ctx);
    }
}

3 涉及到知识点

3.1 jstack 去分析问题, 如何识别出相同的线程 ,观察堆栈信息,运用linux文本命令去分析,awk、sed、sort、uniq

3.2 ps命令, ps -auxH | grep pid | wc -l