• netty该处理耗时业务


    前言

    熟悉 Netty 的同学都知道,不能在 Netty 中做耗时的,不可预料的操作,比如数据库,网络请求,这将会严重影响 Netty 对 Socket 的处理速度。而解决方法就是将耗时任务添加到异步线程池中。但就添加线程池这步操作来讲,可以有2种方式,而且这2种方式的区别也蛮大的。今天就好好讲一讲。

    1. 处理耗时业务的第一种方式-------handler 种加入线程池

    以我们之前的 Netty 的 demo 源码例子,在 channelRead 方法种进行异步:

      @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ThreadPoolExecutor executorPool = ThreadPoolFactoryUtils.CreateThreadPoolIfAbsent(threadPoolConfig,"service-handler-pool",false);
            ThreadPoolFactoryUtils.printThreadPoolStatus(executorPool);
            executorPool.execute(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    String byteBuf = (String) msg;
                    logger.info(Thread.currentThread().getName() + " server recvied: " +byteBuf);
                    StudentReq studentReq = (StudentReq) S_UtilsXml.getEntity(byteBuf,StudentReq.class);
                    logger.info(Thread.currentThread().getName() + " request data: "+studentReq.toString());
                    Thread.sleep(1000);
                    Student stu = new Student();
                    RepTransaction transaction = new RepTransaction();
                    String repXml = "";
                    if (studentReq != null){
                        stu = studentService.getStudentById(studentReq.getStudentId());
    
                        if (stu!=null){
                            logger.info(Thread.currentThread().getName() + " response data: " + stu.toString());
                            transaction = S_UtilsXml.getRepTransaction(byteBuf);
                            transaction.getHeader().getStatus().setRetCd("000000");
                            transaction.getHeader().getStatus().setDesc("success");
                            repXml = S_UtilsXml.getRepXml(transaction,stu);
                        }else {
                            logger.info(Thread.currentThread().getName() + " response data is null");
                            transaction = S_UtilsXml.getRepTransaction(byteBuf);
                            transaction.getHeader().getStatus().setRetCd("000000");
                            transaction.getHeader().getStatus().setDesc("数据不存在");
                            repXml = S_UtilsXml.getNoDataRepXml(transaction);
                        }
    
                    }else {
                        logger.info(Thread.currentThread().getName() + " 请求报文数据内容为null");
                        transaction = S_UtilsXml.getRepTransaction(byteBuf);
                        transaction.getHeader().getStatus().setRetCd("999999");
                        transaction.getHeader().getStatus().setDesc("false");
                        repXml = S_UtilsXml.getNoDataRepXml(transaction);
                    }
    
    
    
                    logger.info(Thread.currentThread().getName() + " 应答报文: " +repXml);
                    ctx.writeAndFlush(repXml);
                }
            });
    
        }

    上图中的 channelRead 方法,我们模拟了一个耗时 10 秒的操作,于是,我们将这个任务提交到了一个自定义的业务线程池中,这样,就不会阻塞 Netty 的 IO 线程。

    这样操作之后,整个程序的逻辑是这样的:

    解释一下上图,当 IO 线程轮询到一个 socket 事件,然后,IO 线程开始处理,当走到耗时 handler 的时候,将耗时任务交给业务线程池。当耗时任务执行完毕再执行 pipeline write 方法的时候(代码中使用的是 context 的 write 方法,上图画的是执行 pipeline 方法),会将任务这个任务交给 IO 线程。

    下面是 write 方法的源码:

    当判定下个 outbound 的 executor 线程不是当前线程的时候,会将当前的工作封装成 task ,然后放入 mpsc 队列中,等待 IO 任务执行完毕后执行队列中的任务。很明显,下个任务的线程肯定是 IO 线程,因为我们没有设置。

    2. 处理耗时业务的第二种方式-------Context 中添加线程池

    第二种方式是 Netty 建议的方式,在添加 pipeline 中的 handler 时候,添加一个线程池:

     而 handler 中的代码不用做任何修改。

    当我们在调用 addLast 方法添加线程池后,handler 将优先使用这个线程池,如果不添加,将使用 IO 线程。

    所以,当走到 AbstractChannelHandlerContext 的 invokeChannelRead 方法的时候,executor.inEventLoop() 是不会通过的,因为当前线程是 IO 线程,Context(也就是 Handler) 的 executor 是业务线程,所以会异步执行,如下:

    总结: 两种方式的对比和思考

    有什么区别呢?第一种方式在 handler 中添加异步,可能更加的自由,比如如果需要访问数据库,那我就异步,如果不需要,就不异步,异步会拖长接口响应时间。因为需要将任务放进 mpscTask 中。如果不凑巧,IO 时间很短,task 很多,可能一个循环下来,都没时间执行整个 task,导致响应时间达不到指标。

    第二种方式是 Netty 建议的,但是,这么做会将整个 handler 都交给业务线程池。不论耗时不耗时,都加入到队列里,不够灵活。

    再回顾一下我们刚开始的图吧:

  • 相关阅读:
    Python 存储引擎 数据类型 主键
    Python 数据库
    Python 线程池进程池 异步回调 协程 IO模型
    Python GIL锁 死锁 递归锁 event事件 信号量
    Python 进程间通信 线程
    Python 计算机发展史 多道技术 进程 守护进程 孤儿和僵尸进程 互斥锁
    Python 异常及处理 文件上传事例 UDP socketserver模块
    Python socket 粘包问题 报头
    Django基础,Day7
    Django基础,Day6
  • 原文地址:https://www.cnblogs.com/huifeidezhuzai/p/16254583.html
Copyright © 2020-2023  润新知