手写RPC框架(一)

RPC(Remote Procedure Call),即远程过程调用,主要应用在分布式应用中,将服务部署在不同的机器上,通过RPC框架调用远程服务器中的内容。通常RPC框架采用客户端(Consumer)/服务端(Provider)的模式,其主要流程是:

  • 客户端调用函数
  • 将调用信息(调用的类、方法、方法传参等)序列化,并通过socket等方式将序列化信息得到的数据包发送给Provider
  • Provider从数据包中反序列化得到调用信息,Provider执行请求,并将返回结果序列化
  • 将序列化后的数据包发给Consumer,Consumer反序列化接收到的数据

本文主要参考文章如何实现一个简单的RPC,这篇文章仅仅实现了对Calculator这个类的远程调用,缺乏通用性,之后会逐步实现 服务注册,负载均衡,动态代理等功能,逐步完善该RPC框架。

本文实现的是一个计算器类的RPC框架,客户端通过调取程序,服务端来返回计算结果。

  • Provider的计算器类

    接口

    public interface Calculator {
        int add(int a,int b);
        int sub(int a,int b);
        int mul(int a,int b);
        int div(int a,int b);
    }
    

    计算器类的实现

    public class CalculatorImpl implements Calculator{
        @Override
        public int add(int a, int b) {
            return a+b;
        }
    
        @Override
        public int sub(int a, int b) {
            return a-b;
        }
    
        @Override
        public int mul(int a, int b) {
            return a*b;
        }
    
        @Override
        public int div(int a, int b) {
            if(b==0){
                throw new ArithmeticException();
            }
            else{
                return a/b;
            }
        }
    }
    
  • 定义远程调用时,客户端发送给服务端的消息体,因为该类需要序列化后传递,所以需要实现Serializable

    package request;
    
    import java.io.Serializable;
    
    public class CalculateRpcRequest implements Serializable {
        private static final long serialVersionUID = 7503710091945320739L;
        private int a;
        private int b;
        private String name;
    
        public int getA() {
            return a;
        }
    
        public void setA(int a) {
            this.a = a;
        }
    
        public int getB() {
            return b;
        }
    
        public void setB(int b) {
            this.b = b;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "CalculateRpcRequest{" +
                    "a=" + a +
                    ", b=" + b +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
  • 定义远程调用时Consumer的逻辑

    public class CalculatorRemoteImpl implements Calculator {
    	//重写Calculator中的方法
        @Override
        public int add(int a, int b) {
            //远程调用的地址
            String address="127.0.0.1";
            try{
                //和远程服务建立socket连接
                Socket socket=new Socket(address,PORT);
                //消息体序列化
                CalculateRpcRequest calculateRpcRequest=generateRequest(a,b);
                //初始化对象的序列化流,把对象转成字节数据,并通过socket发送
                ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
                //写入调用的消息体
                objectOutputStream.writeObject(calculateRpcRequest);
    			//获取返回数据
                ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
                Object response =objectInputStream.readObject();
                System.out.println(response);
                if(response instanceof Integer){
                    return (Integer) response;
                }
                else{
                    throw new InternalError();
                }
            } catch (Exception e) {
                System.out.println("ERROR!");
                throw new InternalError();
            }
        }
    
    	//消息体实例化
        public CalculateRpcRequest generateRequest(int a,int b){
            CalculateRpcRequest calculateRpcRequest=new CalculateRpcRequest();
            calculateRpcRequest.setA(a);
            calculateRpcRequest.setB(b);
            calculateRpcRequest.setName("add");
            return calculateRpcRequest;
        }
        public static final int PORT = 8080;
    }
    
  • 定义远程调用时Provider的逻辑

    public class Provider {
        private final Calculator calculator = new CalculatorImpl();
    
        public static void main(String[] args) throws IOException {
            new Provider().run();
        }
    
        public void run() throws IOException {
            //8080端口上监听数据
            ServerSocket listener = new ServerSocket(8080);
            try {
                Socket socket = listener.accept();
                while (true) {
                    try {
                        //反序列化数据
                        ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                        Object object = objectInputStream.readObject();
                        int res = 0;
                        if (object instanceof CalculateRpcRequest) {
                            CalculateRpcRequest calculateRpcRequest = (CalculateRpcRequest) object;
                            //判断执行方法
                            if ("add".equals(calculateRpcRequest.getName())) {
                                //执行该方法
                                res = calculator.add(calculateRpcRequest.getA(), calculateRpcRequest.getB());
                            } else {
                                throw new UnsupportedOperationException();
                            }
                        }
                        //返回数据序列化
                        ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
                        objectOutputStream.writeObject(new Integer(res));
                    } catch (Exception e) {
                        System.out.println("反序列失败!");
                    } finally {
                        socket.close();
                    }
                }
            } finally {
                listener.close();
            }
        }
    }
    
  • 通过RPC远程调用

    public class Consumer {
    
        public static void main(String[] args) {
            //实例化,发起rpc请求
    		CalculatorRemoteImpl cal=new CalculatorRemoteImpl();
            //远程调用
    		int res=cal.add(1000,210);
    		System.out.println("res= "+res);
    
        }
    }
    

本次只熟悉了上篇文章中的RPC基本的流程和原理,且该框架中通过直接代理Calculator类的方式,难以对项目进行扩展,不能算一个真正意义的RPC框架。之后会通过Java中通过反射实现动态代理的方式将RPC框架完善。

项目地址:https://github.com/iven98/irpc.git (v1.0)