Netty(3)Netty异步模型与任务队列

前言

前言简单的了解了Netty的模型、实现原理和一个简单案例
Netty(2)Netty模型与案例

在这里插入图片描述

这里学习一下Netty中的异步操作与任务队列

同步与异步

在这里插入图片描述
NIO本质上也是同步的,Netty框架是基于NIO的,不过Netty自己完成了异步操作

同步与异步

同步:一个进程执行任务时,会一直等待到当前任务完成才会进行下一个任务
异步:是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理

异步的效率明显会优于同步,网页的AJAX技术就是异步执行的

NIO虽然是多路复用,但是当线程执行某一个操作,其他操作都要进行等待

阻塞问题

Netty如果不做特定的操作
当有耗时高的业务,handler一样会阻塞
例:
以线程睡眠10秒模拟耗时高的业务

package Netty.Simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.util.CharsetUtil;

import java.nio.ByteBuffer;

/*
* 1.自定义的handler需要继承netty规定的handlerAdapter
*重写一些方法,自定义的handler才能称为一个handler
* */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    //读取客户端发送的数据
    //ChannelHandlerContext是上下文对象,含有管道pipeline,通道channel
    //Object msg客户端发送的数据,默认Object
    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {


        //当有耗时高的业务时
       
        Thread.sleep(10*1000);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello , 阻塞10秒",  CharsetUtil.UTF_8));


        System.out.println("Sever go on ...");
    }
    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        //write加Flush方法,将数据写入缓存并刷新
        //对发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端",CharsetUtil.UTF_8));



    }

    //处理异常,关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

在这里插入图片描述

客户端阻塞了10秒才接收到消息
这明显不符合需求

任务队列

Netty模型中NioEventLoop线程还有一个TaskQueue任务队列对象
TaskQueue有三个使用场景

  1. 用户自定义的普通任务
  2. 用户自定义定时任务
  3. 非当前Reactor线程调用Channel的方法(推送系统)

任务队列可以处理上述高耗时任务的问题

用户自定义的普通任务

修改一下客户端pipeline的处理器:

同样是阻塞10秒

package Netty.Simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.util.CharsetUtil;

import java.nio.ByteBuffer;

/*
* 1.自定义的handler需要继承netty规定的handlerAdapter
*重写一些方法,自定义的handler才能称为一个handler
* */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    //读取客户端发送的数据
    //ChannelHandlerContext是上下文对象,含有管道pipeline,通道channel
    //Object msg客户端发送的数据,默认Object
    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        //当有耗时高的业务时- 通过异步解决
        //提交该channel对于的NIOEventLoop的taskQueue上

        //1.用户自定义的普通任务

        ctx.channel().eventLoop().execute(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(10*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello , 阻塞10秒",  CharsetUtil.UTF_8));
            }
        });

/*
        Thread.sleep(10*1000);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello , 阻塞10秒",  CharsetUtil.UTF_8));
*/


        System.out.println("Sever go on ...");
    }
    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        //write加Flush方法,将数据写入缓存并刷新
        //对发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端",CharsetUtil.UTF_8));



    }

    //处理异常,关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

在这里插入图片描述
TaskQueue实现了异步处理,先执行channelReadComplete,再执行阻塞事件

当然队列可以放置多个任务:
再放一个阻塞20秒的任务

        ctx.channel().eventLoop().execute(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(20 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello , 阻塞20秒",  CharsetUtil.UTF_8));
            }
        });

在这里插入图片描述

运行时会将这两个任务放到TaskQueue队列中

ctx - > pipeline - > channel - > enentLoop - > taskQueue
在这里插入图片描述
任务在taskQueue运行是顺序的,并且是一次运行单个任务

在每个任务输出时间来检验

System.out.println("现在时间是:"+new Date());

在这里插入图片描述

阻塞了30秒

定时任务

用户自定义定时任务与普通任务区别并不大,有一点不同:将任务提交到scheduleTaskQueue

在channelRead方法中添加:

        //2.用户自定义定时任务-》任务提交到scheduleTaskQueue
        ctx.channel().eventLoop().schedule(new Runnable() {

            public void run() {
                try {
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello , scheduleTaskQueue阻塞10秒",  CharsetUtil.UTF_8));
            }
        },5, TimeUnit.SECONDS);

在这里插入图片描述
当然,也是顺序执行

ctx - > pipeline - > channel - > enentLoop - > scheduledTaskQueue
在这里插入图片描述
其中还有一些参数

非当前Reactor线程调用Channel的方法

例如:
推送系统的业务线程里面,根据用户的标识,找到对应的 Channel 引用,然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。最终的 Write 会提交到任务队列中后被异步消费

可以使用一个集合管理所有的SocketChannel,当我们想要推送消息时,将业务加入到该Channel对应的NioEventLoop的TaskQueue或者ScheduleTaskQueue

其实操作与上面两种类似
服务器使用集合保存管理所有的Channel,ctx.channel()获得想要的通道, ctx.channel().eventLoop().schedule()或者.execute()操作调用Write方法向该用户推送消息

(其实我们前面已经往客户端推送消息了,这里只是对推送的通道做一定的限制)

异步模型

上面的任务队列的三种应用场景都是异步的,可以处理高耗时问题
接下来了解Netty异步模型

Netty的IO操作其实都是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture

ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9999).sync();

ChannelFuture :The result of an asynchronous {@link Channel} I/O operation

ChannelFuture 是一个异步IO结果
Client并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制(添加监听器Listener)获得 IO 操作结果(在未来才能知道这个结果是成功还是失败)

Netty的异步模型建立在Future与Callback上,处理逻辑是handler

责任链:入站事件与出站事件,pipeline中的处理业务过程:

在这里插入图片描述
在这些handler处理中,可以使用future或者callback实现异步

Future是未来返回结果,它的异步实现也就是对于一个业务,立刻返回Future对象,通过该对象监控业务的处理过程,即通过状态、通知和回调异步获得结果

Future

Java中定义了Future类,在Netty中进行了扩充
接口主要追加了两个功能:

  1. 增加了判断任务是否成功失败的方法,以及失败获取异常信息;
  2. 增加了任务完成时触发的监听器

在这里插入图片描述

Future说明

  • 表示异步的执行结果, 可以通过它提供的方法来检测执行是否完成,比如检索计算等等
  • ChannelFuture 是一个接口
    可以添加监听器,当监听的事件发生时,就会通知到监听器

ChannelFuture接口继承Future接口
在这里插入图片描述有多个实现子类,等以后运用时再详细了解
在这里插入图片描述

Future - Listener机制

当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作

        ChannelFuture future = serverBootstrap.bind(9999).sync();
        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()){
                        System.out.println("服务器监听端口成功");
                }
            }
        });

在这里插入图片描述

可以得到当前操作的状态

总结

  1. NIO是同步的,Netty基于NIO并进行异步操作的修改
  2. Netty对于高耗时的任务可以通过任务队列实现异步
  3. 任务队列有3种使用场景:用户自定义普通任务;定时任务;推送系统
  4. 用户自定义普通任务是将任务加入到TaskQueue中;定时任务是将任务加入scheduleTaskQueue中;推送系统在这两者基础上限制需要的channel
  5. Netty异步模型:pipeline的出栈入栈操作中通过Future或callback实现异步
  6. Future是未来返回结果,即Client连接时,立刻生成一个ChannelFuture对象,根据Future-Listener机制,获得操作执行的状态
发布了95 篇原创文章 · 获赞 25 · 访问量 4183

猜你喜欢

转载自blog.csdn.net/key_768/article/details/104768451