Java-第十七部分-NIO和Netty-taskQueue、异步模型和Netty-HTTP服务

639 阅读4分钟

NIO和Netty全文

taskQueue

  • task的三种应用场景
  1. 用户程序自定义的普通任务,主动提交
  2. 用户自定义定时任务
  3. 非当前Reactor线程调用Channel的各种方法,推送系统的业务线程里,根据用户的标识,找到对应的Channel引用,然后调用Write类方法,向该用户推送消息,最终的Write会提交到任务队列中,后被异步消费

用户程序自定义普通任务

  • 排队执行,第二个等第一个执行完,才会执行
  • 与当前channel用的是一个线程,且当函数中主代码执行完后,才会进行执行
//先执行非任务队列中的代码
//耗时操作 -> 异步执行 -> 提交channel对应的taskQueue中执行
//用户程序自定义普通任务
ctx.channel().eventLoop().execute(new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(10000);
            ctx.writeAndFlush(Unpooled.copiedBuffer("after long time...", CharsetUtil.UTF_8));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
ctx.channel().eventLoop().execute(new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(20000);
            ctx.writeAndFlush(Unpooled.copiedBuffer("after long time...", CharsetUtil.UTF_8));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

image.png

用户自定义定时任务

  • 提交到scheduleTaskQueue
  • 参数Runnable/时间/单位
  • 如果当前线程有其他任务,延迟完毕后,会等待其他任务执行完后执行
ctx.channel().eventLoop().schedule(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("schehulTaskeQueue1 " + i);
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}, 5, TimeUnit.SECONDS);

image.png

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

  • 调用其他线程的Channel的方法
  • 初始化bootstrap时,实现的ChannelInitializer中的initChannel获取各个客户端的channel
protected void initChannel(SocketChannel ch) throws Exception {
    // 使用一个集合管理所有client channel
    // 在推送消息时,将业务加入到各个channel对应的`NIOEventLoop`的`taskQueue/schedulTaskQueue`
    System.out.println("client: " + ch.hashCode());
    set.add((NioSocketChannel) ch);
    //返回channel对应的pipeline,并增加处理器
    ch.pipeline().addLast(new ServerHandler());
}
  • 使用
for (NioSocketChannel sc : Server.set) {
    if (ctx.channel().hashCode() == sc.hashCode()) continue;
    sc.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                sc.writeAndFlush(Unpooled.copiedBuffer(Thread.currentThread().getName() + " after " + (System.currentTimeMillis() - start) + " hello, client" , CharsetUtil.UTF_8));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}

异步模型

  • 当一个异步过程调用发出后,调用者不能立刻得到结果,完成处理后,通过状态、通知和回调来通知调用者
  • Netty中的Bind、Write、Connect等操作,都会简单的返回一个ChannelFuture
  1. 调用者不能立刻获得结果,而是通过Future-Listener机制,主动获取或者通过通知机制获取IO操作结果
  2. Netty的异步模型,建立在FutureCallBack上,假设一个方法,计算过程非常耗时,调用该方法时,立刻返回一个Future,后续通过Future监听方法的处理过程
  • Future
  1. 异步执行结果,通过其方法,检查执行是否完成,
  2. ChannelFuture是一个接口,可以添加监听器,当监听的事件发生时,通知到监听器
  • Netty工作原理中,拦截操作和转换出入栈的操作,只需要提供CallBack或使用Future,即可实现异步实现 image.png image.png

Future-Listener机制

  • Future对象创建时,处于非完成状态,调用者可以通过返回的ChannelFuture来获取操作执行的状态,注册监听函数来执行完成之后的操作
  1. isDone当前操作是否已经完成
  2. isSuccess已完成的当前步骤是否成功
  3. getCause已完成的当前操作失败的原因
  4. isCancelled已完成的当前操作是否被取消
  5. addListener注册监听器,当前操作已完成(isDone方法返回完成),将会通知指定的监听器;如果Future对象已完成,通知指定的监听器
  • 简单的案例
ChannelFuture cf = bootstrap.bind(8888).sync();
//注册监听器
cf.addListeners(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        if (future.isSuccess()) {
            System.out.println("success");
        } else {
            System.out.println("failed");
        }
    }
});
  • 小结
  1. 相比传统阻塞IO,IO操作后线程被阻塞,直到操作完成
  2. 异步处理不会造成线程阻塞,线程在IO操作期间可以执行别的操作,在高并发情形更稳定和更高的吞吐量

添加到异步线程池

  • handler中加入,更加自由,需要就异步,但是会拖长接口响应时间,需要将任务放进mpscTask,如果IO时间很多,task很多,可能导致没时间执行整个task,导致响应时间达不到task完成
  • Context中加入,会将整个handler,进行异步处理,不够灵活

handler中加入

  • EventExecutorGroup
//异步线程池,可以将任务提交到该线程
final static EventExecutorGroup group = new DefaultEventExecutorGroup(16);

image.png

group.submit(new Callable<Object>() {
    @Override
    public Object call() throws Exception {
        //接受客户端信息
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        Thread.sleep(10000);
        System.out.println(Thread.currentThread().getName());
        System.out.println(Thread.currentThread().getId());
        System.out.println(new String(bytes, CharsetUtil.UTF_8));
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello client", StandardCharsets.UTF_8));
        return null;
    }
});
  • 执行完成耗时任务,调用writeAndFlush,将任务交给IO线程,调用outBound出站数据 image.png

Context中加入

  • 线程池,轮询使用,handler正常方式写业务
static final EventExecutorGroup group = new DefaultEventExecutorGroup(2);
  • handler添加时,将handler添加到线程池中,不添加时优先使用IO线程
p.addLast(group, new EchoServerHandler());

Netty-HTTP服务

  • 每次新的连接,就会产生新的handlerpipeline image.png

服务端

public class HttpServer {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new HttpServerInitializer());
            ChannelFuture cf = bootstrap.bind(8888).sync();
            System.out.println("dwada");
            cf.channel().closeFuture().sync();
            System.out.println("dwada");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

handler

//HttpObject 指定客户端和服务端相互通信的数据的封装对象
//SimpleChannelInboundHandler 是 ChannelInboundHandlerAdapter 的子类
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    //当有读取事件时触发
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest) {
            System.out.println("pipe: " + ctx.pipeline().hashCode() + " - handler: " + this.hashCode());
            System.out.println("msg type: " + msg.getClass());
            System.out.println("client address: " + ctx.channel().remoteAddress());
            //屏蔽客户端的favicon.ico请求
            HttpRequest httpRequest = (HttpRequest) msg;
            //获取请求的uri
            URI uri = new URI(httpRequest.uri());
            if ("/favicon.ico".equals(uri.getPath())) {
                System.out.println("请求了 favicon.ico");
                return;
            }
            //回复信息 谷歌浏览器 UTF_16 不乱码;UTF_8 乱码
            ByteBuf content = Unpooled.copiedBuffer("你好 hello client", CharsetUtil.UTF_16);
            //构造响应
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            //设置响应头
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
            //发送
            ctx.writeAndFlush(response);
        }
    }
}

Initializer

public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        //加入处理器
        //编解码处理器
        ChannelPipeline pipeline = ch.pipeline();
        //HttpServerCodec netty 提供的处理http的编解码器
        pipeline.addLast("MyHttpServerCodeC", new HttpServerCodec());
        //增加自定义的处理器
        pipeline.addLast("MyHttpServerHandler", new HttpServerHandler());
    }
}