Java-第十七部分-NIO和Netty-RPC

151 阅读3分钟

NIO和Netty全文

RPC

  • Remote Procedure Call,远程过程调用,计算机通信协议,允许运行于一台计算机的程序,调用另一台计算机的子程序,无需额外为这个交互作用编程

两个或多个应用程序都分布在不同服务器上,之间的调用像是本地调用 image.png

  • 常用RPC框架,阿里Dubbo、谷歌gRPC、GO语言的rpcx、Apache的thtift、Spring Cloud
  • 调用流程,将2-7封装 image.png
  1. 客户端(服务消费者)请求服务
  2. clientStub接收到调用,将方法、参数进行封装
  3. 编码,并发送服务端(服务生产方)
  4. serverStub接受,并解码
  5. 调用API,并响应,生成结果
  6. serverStub编码,并发送给客户端
  7. clientStub接受,并解码
  8. 最终调用者得到结果

dubbo RPC

  • 接口,定义抽象方法,用于消费者和提供者之间的约定
  • 提供者,该类监听消费者的请求,按照约定返回数据
  • 消费者,透明地调用自己不存在的方法,内部使用Netty请求提供者发挥数据 image.png

公共接口

public interface IHelloService {
    String hello(String msg);
}

NettyServer

public class NettyServer {
    public static void startServer(String hostname, int port) {
        startServer0(hostname, port);
    }
    //初始化和启动
    private static void startServer0(String hostname, int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new NettyServerHandler());
                        }
                    }).bind(hostname, port).sync().channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

HelloServiceImpl

  • 服务端的提供服务接口
public class HelloServiceImpl implements IHelloService {
    //count不变,每次调用,都会生成一个HelloServiceImpl实例
    private int count = 0;
    //当消费方调用,返回结果
    @Override
    public String hello(String msg) {
        System.out.println("收到: " + msg);
        //根据msg返回不同结果
        if (msg != null) {
            return "已经收到消息 [ " + msg + " ] 第 " + count++ + " 次...";
        } else {
            return "收到消息为空...";
        }
    }
}

NettyServerHandler

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //获取客户端发送的消息,并调用服务
        System.out.println("netty server receive: " + msg);
        //客户端再调用服务器服务时,需要定义一个协议
        //每次发消息,都必须以某个字符串开头
        if (msg.toString().startsWith("HelloService#hello#")) {
            //截取真正的内容
            String res = new HelloServiceImpl().hello(
                    msg.toString().substring(
                            msg.toString().lastIndexOf("#") + 1));
            //回写
            ctx.writeAndFlush(res);
        } else {
            System.out.println("prefix error");
            ctx.writeAndFlush("prefix error");
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

NettyClient

  • TCP_NODELAY允许小包发送
  • 使用代理调用实现异步调用远程服务
public class NettyClient {
    private static ExecutorService executorService = Executors.newFixedThreadPool(5);
    private static  NettyClientHandler clientHandler;
    //使用代理模式,获取代理对象
    public Object getBean(final Class<?> serviceClass, final String prefix) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] {serviceClass}, //接口数组
                ((proxy, method, args) -> {
                    if (clientHandler == null) {
                        initClient();
                    }
                    //设置发给服务器端的信息 协议头 + 第一个参数
                    clientHandler.setPara(prefix + args[0]);
                    //get() 获取 res结果
                    return executorService.submit(clientHandler).get();
                }));
    }

    //初始化客户端
    private static void initClient() {
        clientHandler = new NettyClientHandler();
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            new Bootstrap().group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(clientHandler);
                        }
                    }).connect("localhost", 9999).sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //不能关闭,不能阻塞,因为这是一个函数调用,执行完之后,才能执行下面的代码
            //group.shutdownGracefully();
        }
    }
}

NettyClientHandler

  • 实现Callable,直到有返回值时,才继续执行,否则wait()等待
  • channelRead直到读到数据,唤醒线程
public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {
    private ChannelHandlerContext context;
    private String res;
    private String para; //客户端调用方式时的参数
    //channelActive -> setPara -> call -> channelRead -> call
    public void setPara(String para) {
        this.para = para;
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive");
        //在其他方法,需要使用上下文
        context = ctx;
    }

    //第一个被调用
    //synchronized 同步执行
    @Override
    public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("channelRead");
        res = msg.toString();
        notify(); //拿到结果后,唤醒等待的线程
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
    //被代理对象调用,发送数据给服务器,并且等待被唤醒,因此要同步执行
    @Override
    public synchronized Object call() throws Exception {
        System.out.println("call");
        context.writeAndFlush(para);
        //唤醒后,拿到结果
        wait();
        System.out.println("call");
        return res; //服务方返回的结果
    }
}

Bootstrap

  • Client
public class ClientBootstrap {
    //协议头
    public static final String PROVIDERNAME = "HelloService#hello#";
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        NettyClient customer = new NettyClient();
        IHelloService server = (IHelloService) customer.getBean(IHelloService.class, PROVIDERNAME);
        while (true) {
            Thread.sleep(5000);
            //实际调用的是代理对象的方法
            String res = server.hello("你好 dubbo~");
            System.out.println("res: " + res);
        }
    }
}
  • Server
public class ServerBootstrap {
    public static void main(String[] args) {
        NettyServer.startServer("localhost", 9999);
    }
}