• 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 都交给业务线程池。不论耗时不耗时,都加入到队列里,不够灵活。

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

  • 相关阅读:
    KindEditor粘贴word图片且图片文件自动上传功能
    umeditor粘贴word图片且图片文件自动上传功能
    百度Web编辑器粘贴word图片且图片文件自动上传功能
    PHP大文件上传支持断点上传解决方案
    PHP大文件上传支持断点上传教程
    matlab函数——shading函数
    Matlab griddata函数功能介绍
    戴尔 SE2416HM
    visio中如何旋转形状
    axios的封装与异常处理(async/await)
  • 原文地址:https://www.cnblogs.com/huifeidezhuzai/p/16254583.html
Copyright © 2020-2023  润新知