实现RPC框架

实现RPC框架

1.RPC原理

什么是RPC

  • RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
  • 本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
  • 远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?
    1.首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
    2.客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
    3.数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。
// Client端 
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client

我们通过一张图来大概了解一下在一个完整的RPC框架中存在的角色以及整个远程调用的过程。
在这里插入图片描述
通过上面的图可以看出来,在RPC框架中主要有以下4个角色:

  • registry - 注册中心,当服务提供者启动时会向注册中心注册,然后注册中心会告知所有的消费者有新的服务提供者。
  • provider - 服务提供者,远程调用过程中的被消费方。
  • consumer - 服务消费者,远程调用过程中的消费方。
  • monitor - 监视器,它主要负责统计服务的消费和调用情况。

启动服务提供者后,服务提供者会以异步的方式向注册中心注册。然后启动服务消费者,它会订阅注册中心中服务提供者列表,当有服务提供者的信息发生改变时,注册中心会通知所有的消费者。当消费者发起远程调用时,会通过动态代理将需要请求的参数以及方法签名等信息通过Netty发送给服务提供者,服务提供者收到调用的信息后调用对应的方法并将产生的结果返回给消费者,这样就完成了一个完整的远程调用。当然了这个过程中可能还会将调用信息异步发送给monitor用于监控和统计。

2.编码实现

2.1 服务端
服务端接口

/**
     * 启动rpc服务
     * @throws IOException
     */
    void start() throws IOException;

    /**
     * 服务注册
     * @param serviceInterface 服务接口
     * @param impl 服务实现类
     */
    void register(Class serviceInterface, Class impl);

服务端实现

@Override
    public void start() throws IOException {
        ServerSocket server = new ServerSocket();
        server.bind(new InetSocketAddress(port));
        System.out.println("Bio rpc 服务启动开始###端口###" + port);
        try {
            while (true) {
                System.out.println("等待客户端接入");
                executor.execute(new ServersTask(server.accept()));
            }
        } catch (Exception e) {
            if (server != null) {
                server.close();
            }
        }
    }

    @Override
    public void register(Class serviceInterface, Class impl) {
        serviceRegistry.put(serviceInterface.getName(), impl);
    }

    static class ServersTask implements Runnable {
        Socket client = null;

        public ServersTask(Socket socket) {
            this.client = socket;
        }

        ObjectInputStream input = null;//输入流
        ObjectOutputStream output = null;//输出流

        @Override
        public void run() {
            //接收客户端发送来的字节流,并转化成对象,反射调用服务实现者,获取执行结果
            try {
                input = new ObjectInputStream(this.client.getInputStream());
                String interfaceName = input.readUTF();
                String methodName = input.readUTF();
                Class<?>[] parameterType = (Class<?>[]) input.readObject();
                Object[] args = (Object[]) input.readObject();
                Class serverClass = serviceRegistry.get(interfaceName);
                if (serverClass == null) {
                    throw new ClassNotFoundException(interfaceName + " not found");
                }
                Method method = serverClass.getMethod(methodName, parameterType);
                Object result = method.invoke(serverClass.newInstance(), args);
                //执行结果反序列化返回给客户端
                output = new ObjectOutputStream(this.client.getOutputStream());
                output.writeObject(result);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (output != null) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (this.client != null) {
                    try {
                        this.client.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }

2.2 客户端代理

public static <T> T create(final Class<?> serviceInterface, final String ip, final int port) {
        //将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface}, new ProxyHandler(ip, port, serviceInterface));
    }

    static class ProxyHandler implements InvocationHandler {
        private String ip;
        private int port;
        private Class<?> serviceInterface;

        public ProxyHandler(String ip, int port, Class<?> serviceInterface) {
            this.ip = ip;
            this.port = port;
            this.serviceInterface = serviceInterface;
        }

在invoke中我们需要将参数,方法名等传入到服务端,然后等待返回服务端的结果
连接服务端

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Socket socket = null;
            ObjectOutputStream output = null;
            ObjectInputStream input = null;
            try {
                // 创建Socket客户端,根据指定地址连接远程服务提供者
                socket = new Socket(ip, port);
                // 将远程服务调用所需的接口类、方法名、参数列表等编码后发送给服务提供者
                output = new ObjectOutputStream(socket.getOutputStream());
                output.writeUTF(serviceInterface.getName());
                output.writeUTF(method.getName());
                output.writeObject(method.getParameterTypes());
                output.writeObject(args);
                // 同步阻塞等待服务器返回应答,获取应答后返回
                input = new ObjectInputStream(socket.getInputStream());
                return input.readObject();
            } finally {
                if (socket != null) {
                    socket.close();
                }
                if (output != null) {
                    output.close();
                }
                if (input != null) {
                    input.close();
                }
            }

客户端调用

long start =System.currentTimeMillis();
        for (int i=0;i<200;i++){
            try {
                IOrderService orderService= ProxyFactory.create(IOrderService.class,"127.0.0.1",8088);
                System.out.println(orderService.getOrderDtoByUserId(i));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        long end=System.currentTimeMillis();
        System.out.println("总耗时:"+(end-start));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值