手寫RPC框架(六)整合Netty

流光之中發表於2022-02-18

手寫RPC框架(六)整合Netty

Netty簡介:

Netty是一個基於NIO的,提供非同步,事件驅動的網路應用工具,具有高效能高可靠性等特點。

使用傳統的Socket來進行網路通訊,服務端每一個連線都要新建一個執行緒,清楚處理完成後通過輸出流返回給客戶端。而Netty通過NIO的方式,服務端實現為一個請求一個執行緒,客戶端傳送的連線請求會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才會啟動一個執行緒進行處理。

這次我們通過Netty來實現網路通訊,替代Socket,提高框架效能。

  1. 引入Netty

    <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.73.Final</version>
    </dependency>
    
  2. netty服務端

    public class NettyServer{
        private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
        private String serverAddress; //啟動地址
        private int serverPort; //啟動埠
    
        private EventLoopGroup boss = null;
        private EventLoopGroup worker = null;
    
        public NettyServer(String serverAddress, int serverPort) {
            this.serverAddress = serverAddress;
            this.serverPort = serverPort;
        }
    
        public void startNettyServer() throws Exception {
            //netty排程模組,負責接收請求
            NioEventLoopGroup bossGroup = new NioEventLoopGroup();
            //netty排程模組,負責處理請求
            NioEventLoopGroup workGroup = new NioEventLoopGroup();
            //啟動類
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup,workGroup);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    //傳輸資料的channel
                    ChannelPipeline pipeline = ch.pipeline();
                    //解碼器
                    pipeline.addLast(new StringDecoder());
                    //編碼器
                    pipeline.addLast(new StringEncoder());
                    //業務邏輯
                    pipeline.addLast(new RpcServerHandler());
                }
            });
    
            try {
                //埠繫結
                ChannelFuture sync = bootstrap.bind(serverAddress, serverPort).sync();
                sync.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                bossGroup.shutdownGracefully();
                workGroup.shutdownGracefully();
            }
        }
    
    
        @PreDestroy
        public void destory() throws InterruptedException {
            boss.shutdownGracefully().sync();
            worker.shutdownGracefully().sync();
            logger.info("關閉Netty");
        }
    }
    

    在這裡通過startServer方法會啟動netty服務端,當有請求時,會進入RpcServerHandler()方法中進行處理。

    @ChannelHandler.Sharable
    public class RpcServerHandler extends SimpleChannelInboundHandler<String> {
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) {
            //msg為接收到的請求資訊
            RpcResponse response = new RpcResponse();
            //將請求資訊解碼
            RpcRegisterEntity rpcRegisterEntity=JSON.parseObject(msg,RpcRegisterEntity.class);
            //通過反射得到遠端呼叫的類並執行該方法
            Object result = invoke(rpcRegisterEntity);
            try {
                //返回體
                response.setResult(result);
            } catch (Exception exception) {
                exception.printStackTrace();
                response.setException(exception);
            }
            //寫入返回資料
            ctx.writeAndFlush(JSON.toJSONString(response));
        }
    
        private Object invoke(RpcRegisterEntity entity) {
            try {
                //介面名
                String interfaceName = entity.getServiceImplClassFullName();
    //            String implClassName = RegisterCenter.getProviderData(interfaceName);
                //類名
                String implClassName = entity.getServiceImplClassFullName();
                Class<?> clazz = Class.forName(implClassName);
                String methodName = entity.getMethodName();
                Class<?>[] parameterTypes = entity.getParameterTypes();
                Object[] parameters = entity.getParameters();
                Method method = clazz.getMethod(methodName, parameterTypes);
                //通過反射得到結果
                return method.invoke(clazz.newInstance(), parameters);
            } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
                return e;
            }
        }
        //當Channel處理於活動狀態時被呼叫
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
    //        System.out.println(ctx.channel().remoteAddress().toString());
            super.channelActive(ctx);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    //        System.out.println(JSON.toJSONString(cause));
            super.exceptionCaught(ctx, cause);
        }
    }
    
  3. netty消費端

    public class RpcClient{
        private static final Logger logger = LoggerFactory.getLogger(RpcClient.class);
        private EventLoopGroup group;
    
        private Channel channel;
    
        private String ip;
    
        private int port;
    
        private RpcConsumerHandler rpcConsumerHandler=new RpcConsumerHandler();
    
        private ExecutorService executorService = Executors.newCachedThreadPool();
    
        public RpcClient(String ip, int port) {
            this.ip = ip;
            this.port = port;
            initClient();
        }
    
        public void initClient() {
            try {
                //1.建立執行緒組
                group = new NioEventLoopGroup();
                //2.建立啟動助手
                Bootstrap bootstrap = new Bootstrap();
                //3.設定引數
                bootstrap.group(group)
                        //傳輸資料用的channel
                        .channel(NioSocketChannel.class)
                        .option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
                        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel channel) throws Exception {
                                ChannelPipeline pipeline = channel.pipeline();
                                pipeline.addLast(new StringEncoder());
                                pipeline.addLast(new StringDecoder());
                                //新增客戶端處理類
                                pipeline.addLast(rpcConsumerHandler);
                            }
                        });
                //4.連線Netty服務端
                connect(bootstrap, ip, port, 5);
            } catch (Exception exception) {
                exception.printStackTrace();
                if (channel != null) {
                    channel.close();
                }
                if (group != null) {
                    group.shutdownGracefully();
                }
            }
        }
    
        private void connect(Bootstrap bootstrap, String host, int port, int retry) {
            ChannelFuture channelFuture = bootstrap.connect(host, port).addListener(future -> {
                if (future.isSuccess()) {
                    logger.info("連線服務端成功");
                } else if (retry == 0) {
                    logger.error("重試次數已用完,放棄連線");
                } else {
                    //第幾次重連:
                    int order = (5 - retry) + 1;
                    //本次重連的間隔
                    int delay = 1 << order;
                    logger.error("{} : 連線失敗,第 {} 重連....", new Date(), order);
                    bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS);
                }
            });
            channel = channelFuture.channel();
        }
        /**
         * 提供給呼叫者主動關閉資源的方法
         */
        public void close() {
            if (channel != null) {
                channel.close();
            }
            if (group != null) {
                group.shutdownGracefully();
            }
        }
    
        /**
         * 提供訊息傳送的方法
         */
        public Object send(String msg) throws ExecutionException, InterruptedException {
            rpcConsumerHandler.setRequestMsg(msg);
            Future submit = executorService.submit(rpcConsumerHandler);
            return submit.get();
        }
    
        public void destroy() throws Exception {
            if (channel != null) {
                channel.close();
            }
            if (group != null) {
                group.shutdownGracefully();
            }
        }
    
        public String getIp() {
            return ip;
        }
    
        public void setIp(String ip) {
            this.ip = ip;
        }
    
        public int getPort() {
            return port;
        }
    
        public void setPort(int port) {
            this.port = port;
        }
    }
    

    當建立好客戶端時,傳送請求時資料會交由rpcConsumerHandler處理,

    public class RpcConsumerHandler extends SimpleChannelInboundHandler<String> implements Callable {
        ChannelHandlerContext context;
        //傳送的訊息
        String requestMsg;
    
        //服務端返回的訊息
        String responseMsg;
    
        public void setRequestMsg(String requestMsg) {
            this.requestMsg = requestMsg;
        }
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            context = ctx;
        }
        //接收由服務端返回的資料
        @Override
        protected synchronized void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
            System.out.println("客戶端結果:"+msg);
            responseMsg = msg;
            //喚醒等待的執行緒
            notify();
        }
        //傳送資料
        @Override
        public synchronized Object call() throws Exception {
            //訊息傳送
            context.writeAndFlush(requestMsg);
            //執行緒等待
            wait();
            return responseMsg;
        }
    }
    
  4. 呼叫

    //新建連線
    RpcClient rpcClient = getClient(rpcRegisterEntity.getHost(), rpcRegisterEntity.getPort());
    //傳送資料
    Object responseMsg = rpcClient.send(JSON.toJSONString(rpcRegisterEntity));
    //解析返回的資料
    RpcResponse rpcResponse = JSON.parseObject(responseMsg.toString(), RpcResponse.class);
    

相關文章