大话rpc-远程调用那些事

本文介绍了RPC的基本概念以及在微服务背景下的重要性。通过详细步骤展示了如何使用Netty实现一个简单的RPC框架,包括消费端和生产端的实现,涉及到序列化、反序列化和Netty的通信机制。文章最后提到了Dubbo框架,指出Dubbo也基于Netty,但使用Hessian2作为默认序列化方式。
摘要由CSDN通过智能技术生成

RPC全称为Remote Procedure Call,翻译过来为“远程过程调用”

背景:现如今微服务甚是火热,针对单体服务按功能模块各种拆分、拆出了订单中心、商品中心、商户中心等等。一个中心下边又有多个springboot包、多个包 多个进程之间的数据交互就不可避免,跨进程之间的调用就是rpc。

市场上流行的框架是dubbo,那么自己实现一个rpc 该如何下手 ?
1、基于netty实现
2、请求实体 序列化成字节流—>netty传输—>反序列化接收

消费端实现
1.1 基于 netty 获取通道 Channel、同时设置序列化方式 new ObjectEncoder()

public class NettyClient {
    public Channel doOpen(String host, int port) {
        final EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        // 使用NioSocketChannel来作为连接用的channel类
        b.group(group).channel(NioSocketChannel.class)
                // 绑定连接初始化器
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) {
                        ChannelPipeline ph = ch.pipeline();
                        ph.addLast(new ObjectDecoder(1024, ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));
                        ph.addLast(new ObjectEncoder());
                        //客户端处理类
                        ph.addLast(new DubboChannelHandler());
                    }
                });
        //发起异步连接请求,绑定连接端口和host信息
        ChannelFuture future = null;
        try {
            future = b.connect(host, port).sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (future.isSuccess()) {
            System.out.println("连接服务器成功");

        } else {
            System.out.println("连接服务器失败");
            future.cause().printStackTrace();
            group.shutdownGracefully(); //关闭线程组
        }
        return future.channel();
    }
}

1.2 利用接口代理发送请求 this.channel.writeAndFlush(request);

@Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        //代理对象-通道写数据
        DubboRequest request=new DubboRequest();
        request.setId(Math.round(100));
        request.setClassName(clazz.getName());
        request.setMethod(method.getName());
        request.setArgs(args);

        this.channel.writeAndFlush(request);
        //事件监听线程-
        return new DubboFuture(request.getId()).get().getData();

    }

1.3 SimpleChannelInboundHandler 通道事件监听 channelRead0 接收响应

public class DubboChannelHandler extends SimpleChannelInboundHandler<DubboResult> {

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, DubboResult result) {
        //监听接收请求- DubboResult(id=100, data=Hello World你好 呀 我已经进来了 哈哈哈)
        DubboFuture.received(result);

    }
    // ChannelHandlerContext(DubboChannelHandler#0, [id: 0x3f770713, L:/127.0.0.1:58188 - R:/127.0.0.1:8088])
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //获取通道
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
    }
}

生产端实现
2.1 ServerBootstrap 绑事件_EventLoopGroup 监听通道 socketChannel

public class NettyServer {

    public void bind(int port) {
        EventLoopGroup worker = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(worker)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 50000)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) {
                            ChannelPipeline ph = socketChannel.pipeline();
                            ph.addLast(new ObjectDecoder(1024*1024, ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
                            ph.addLast(new ObjectEncoder());
                            //添加NettyServerHandler,用来处理Server端接收和处理消息的逻辑
                            ph.addLast(new DubboChannelHandler());
                        }
                     });

            // 服务器绑定端口监听
            ChannelFuture f = b.bind(port).sync();
            if (f.isSuccess()) {
                System.err.println("启动Netty服务成功,端口号:" + port);
            }
            // 监听服务器关闭监听
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            System.err.println("启动Netty服务异常,异常信息:" + e.getMessage());
            e.printStackTrace();
        } finally {
            worker.shutdownGracefully();
        }
    }
}

2.2 ChannelInboundHandlerAdapter 的 channelRead方法接收客户端 _序列化后 ObjectEncoder() 值

public class DubboChannelHandler extends ChannelInboundHandlerAdapter {
    /**
     * 通道读取数据事件,监听
     * ChannelHandlerContext(DubboChannelHandler#0, [id: 0x99314047, L:/127.0.0.1:8088 - R:/127.0.0.1:57663])
     * DubboRequest(id=100, className=com.nettyRpc.api.IHelloService, method=hello, args=[Hello World])
     *
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        DubboRequest request = (DubboRequest) msg;
        //根据接口-反射实现,拿到结果
        Object result = new InvokeSupport().invoke(request);
        DubboResult response = new DubboResult();
        response.setId(request.getId());
        response.setData(result);
        System.err.println("服务器回复消息:" + JSON.toJSONString(response));
        ctx.writeAndFlush(response);
        //写回信息
    }

    /**
     * 数据读取完毕事件
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        //完毕
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 通道发生异常事件
        super.exceptionCaught(ctx, cause);
    }
}

2.3 此刻已经拿到请求 DubboRequest(id=100, className=com.nettyRpc.api.IHelloService, method=hello, args=[Hello World])、根据 className反射找到实现 invoke即可
2.4 ctx.writeAndFlush(response) 通道响应数据即可。。。。

至此,一个基于netty的简单rpc已经成功。其中涉及了序列化、反序列化、netty 发送数据、监听接收数据等

查看代码前往 基于netty的rpc

ps:dubbo也是基于netty、但是比这复杂很多。默认使用 Hessian2 序列化
#dubbo.protocol.serialization=hessian2

public class Hessian2Serialization implements Serialization {}
    org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization
        public ObjectOutput serialize(URL url, OutputStream out) throws IOException {
            return new Hessian2ObjectOutput(out);
        }

# org.apache.dubbo.remoting.exchange.codec.ExchangeCodec
        protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
            Serialization serialization = this.getSerialization(channel);
            byte[] header = new byte[16];
            Bytes.short2bytes((short)-9541, header);
            header[2] = (byte)(-128 | serialization.getContentTypeId());
            if (req.isTwoWay()) {
                header[2] = (byte)(header[2] | 64);
            }

            if (req.isEvent()) {
                header[2] = (byte)(header[2] | 32);
            }

            Bytes.long2bytes(req.getId(), header, 4);
            int savedWriteIndex = buffer.writerIndex();
            buffer.writerIndex(savedWriteIndex + 16);
            ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
            // 序列化
            ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
            if (req.isEvent()) {
                this.encodeEventData(channel, out, req.getData());
            } else {
                this.encodeRequestData(channel, out, req.getData(), req.getVersion());
            }

            out.flushBuffer();
            if (out instanceof Cleanable) {
                ((Cleanable)out).cleanup();
            }

            bos.flush();
            bos.close();
            int len = bos.writtenBytes();
            checkPayload(channel, (long)len);
            Bytes.int2bytes(len, header, 12);
            buffer.writerIndex(savedWriteIndex);
            buffer.writeBytes(header);
            buffer.writerIndex(savedWriteIndex + 16 + len);
        }

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值