展开
拓展 关闭
订阅号推广码
GitHub
视频
公告栏 关闭

任务使用场景

  • 任务队列中的 Task 有 3 种典型使用场景
1) 用户程序自定义的普通任务 [举例说明]
2) 用户自定义定时任务
3) 非当前 Reactor 线程调用 Channel 的各种方法
    例如在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel 引用,然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。最终的 Write 会提交到任务队列中后被异步消费
  • 在NettyServerHandler类用模拟1个很费事的操作
# 添加如下代码
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        Thread.sleep(5 * 1000);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
    }
}

# 测试
# 启动服务端,再启动客户端
# 服务端控制台打印如下
.....服务器 is ready...
server ctx =ChannelHandlerContext(NettyServerHandler#0, [id: 0xe4f5ca0b, L:/127.0.0.1:6668 - R:/127.0.0.1:56416])
客户端发送消息是:hello, server: (>^ω^<)喵
客户端地址:/127.0.0.1:56416

# 客户端控制台打印如下
客户端 ok..
client ChannelHandlerContext(NettyClientHandler#0, [id: 0xc6a008e7, L:/127.0.0.1:56416 - R:/127.0.0.1:6668])
服务器回复的消息:hello, 客户端~(>^ω^<)喵2
服务器的地址: /127.0.0.1:6668
服务器回复的消息:hello, 客户端~(>^ω^<)喵1
服务器的地址: /127.0.0.1:6668

# 客户端连接到服务端后,阻塞了5秒才打印 《客户端~(>^ω^<)喵2》,最后后服务端返回《客户端~(>^ω^<)喵1》
  • 修改服务端处理器
# 添加如下
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(5 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
                    System.out.println("channel code=" + ctx.channel().hashCode());
                } catch (Exception ex) {
                    System.out.println("发生异常" + ex.getMessage());
                }
            }
        });

        // 使用方案1,再处理1个任务
        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);
    }
}

# 测试
# 启动服务端,再启动客户端
# 服务端控制台打印如下
.....服务器 is ready...
server ctx =ChannelHandlerContext(NettyServerHandler#0, [id: 0xafd22374, L:/127.0.0.1:6668 - R:/127.0.0.1:56738])
客户端发送消息是:hello, server: (>^ω^<)喵
客户端地址:/127.0.0.1:56738

# 客户端控制台打印如下
客户端 ok..
client ChannelHandlerContext(NettyClientHandler#0, [id: 0xaf11456f, L:/127.0.0.1:56738 - R:/127.0.0.1:6668])
服务器回复的消息:hello, 客户端~(>^ω^<)喵1
服务器的地址: /127.0.0.1:6668
服务器回复的消息:hello, 客户端~(>^ω^<)喵2
服务器的地址: /127.0.0.1:6668
服务器回复的消息:hello, 客户端~(>^ω^<)喵3
服务器的地址: /127.0.0.1:6668
服务器回复的消息:hello, 客户端~(>^ω^<)喵4
服务器的地址: /127.0.0.1:6668

# 测试结果:这次,任务2、任务3、任务4并没有影响任务1的执行,任务1先执行后,任务2阻塞5秒后执行,任务3阻塞5秒后执行,任务4阻塞5秒后执行
  • 方式3
# NettyServer类中可以获取到socketchannel的hash码
# 可以使用一个集合管理SocketChannel,再推送消息时,可以将业务加入到各个channel对应的NIOEventLoop的taskQueue或者scheduleTaskQueue
@Override
protected void initChannel(SocketChannel ch) throws Exception {
    System.out.println("客户socketchannel hashcode=" + ch.hashCode()); 
    ch.pipeline().addLast(new NettyServerHandler());
}

# 在NettyServerHandler中同样可以拿到channel的hash码
System.out.println("channel code=" + ctx.channel().hashCode());

# 完整代码中查看
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @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(5 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
                    System.out.println("channel code=" + ctx.channel().hashCode());
                } catch (Exception ex) {
                    System.out.println("发生异常" + ex.getMessage());
                }
            }
        });

        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);

    }
}
  • 小结
1) Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。
2) NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个selector,用于监听绑定在其上的 socket 网络通道。
3) NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由IO 线程 NioEventLoop 负责
• NioEventLoopGroup 下包含多个 NioEventLoop
• 每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
• 每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
• 每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
• 每个 NioChannel 都绑定有一个自己的 ChannelPipeline
posted @ 2022-08-08 15:25  DogLeftover  阅读(18)  评论(0编辑  收藏  举报