手把手教你手写一个RPC框架

前言

最近在看dubbo的源码,所以就参考着这本书籍作为辅助(这个诣极总是让我看成了极诣,嗯,极诣狂战士):

手把手教你手写一个RPC框架_第1张图片

dubbo在3.0版本就有三十万行代码,光看个核心功能的代码就快折磨死人了,为了加强理解,我觉得动手实现一个rpc框架倒是个非常不错的方法。

本文主要会围绕RPC的基本功能来展开。主要是对RPC有个认知,至于SPI、注册中心、负载均衡、netty传递信息等的实现,我会放在下一篇文章中进行讲解。

开篇

市面上有很多RPC框架,虽然种类比较多,但是他们所围绕的中心思想是一样的。我们看下书中是这么介绍的:

手把手教你手写一个RPC框架_第2张图片

这张图就很好的展示了RPC的调用过程:把客户端需要调用的信息传递到服务端,每次服务调用的一些公用的信息包括服务调用接口、方法名、方法参数类型和方法参数值等,在传递方法参数值时 需要先序列化对象并经过网络传输到服务端,在服务端需要按照客户端序列化顺序再做一次反 序列化来读取信息,然后拼装成请求对象进行服务反射调用,最终将调用结果再传给客户端。

所以总结起来呢,整个RPC调用过程可以分为四个部分:

  1. 服务提供者:服务端,他需要将可以被引用的服务注册到注册中心以供消费者使用。

  2. 服务消费者:即客户端,他可以从注册中心去获取自己需要的服务。这里需要注意的是,提供者与消费者只是个相对的概念,一个服务既可以当提供者,也可以当消费者。

  3. 注册中心:保存提供者提供的服务,像dubbo中默认的注册中心是zookeeper,但是它也支持别的中间件作为注册中心,比如像redis、nacos等。在下一篇中我会以zookeeper为注册中心来进行示例。

手把手教你手写一个RPC框架_第3张图片

  1. 监控中心:他可以监控接口的响应时间、统计请求数量等,这个不是必需品,可以省去的。

实现

其实要实现基本的RPC的话,以上这些就够了,我们就直接来看实现吧,为了能更清楚整个调用有哪些部分,我对整个调用过程进行了分块:

手把手教你手写一个RPC框架_第4张图片

如果你想看到RPC调用结果的话,也可以不用像我这样进行分模块,只需要复制我接下来的代码就OK了。

api模块:存放接口的模块,便于提供端provider进行接口的实现。

public interface DemoService {
    String sendAndGetMessage(String message);
}

Common:主要存放这个过程中需要的一些实体类:

Invocation:因为我们客户端需要将信息发送到服务端,所以需要对信息进行一些封装:

public class Invocation implements Serializable {
  //因为要进行网络传输,所以需要进行序列化。
    //接口全限定名
    private String className;
    //方法名
    private String methodName;
    //方法参数类型
    private Class<?>[] paramTypes;
    //方法参数
    private Object[] params;

    /*getter and setter*/

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class<?>[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class<?>[] paramTypes) {
        this.paramTypes = paramTypes;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }
}

URL:因为要进行网络传输,所以需要保存请求的ip、端口:

public class URL implements Serializable {
    private String hostname;
    private Integer port;

    public URL(String hostname, Integer port) {
        this.hostname = hostname;
        this.port = port;
    }

    public String getHostname() {
        return hostname;
    }

    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }
}

定义返回信息:

public class RpcResponse implements Serializable {
    //异常
    private Throwable error;
    //调用结果
    private Object result;

    /*getter and setter*/

    public Throwable getError() {
        return error;
    }

    public void setError(Throwable error) {
        this.error = error;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }
}

Framework:整个传递过程的功能类。

RpcConsumer:消费者的处理逻辑

public class RpcConsumer {
    public Object execute(Invocation invocation, String host, int port) throws Throwable {
        Socket server = new Socket(host, port);
        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        try{
            //将请求写到连接服务端socket的输出流
            oos = new ObjectOutputStream(server.getOutputStream());
            oos.writeObject(invocation);
            oos.flush();

            //读取输入流的内容
            ois = new ObjectInputStream(server.getInputStream());
            Object res = ois.readObject();
            RpcResponse response = (RpcResponse) res;

            return response.getResult();

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw e;
        } finally {
            if (ois!=null)ois.close();
            if (oos != null) oos.close();
            if (server != null) server.close();
        }
    }
}

RpcProvider提供者的处理逻辑:

public class RpcProvider {
    public void start(int port, Map<String, Object> services){
        ServerSocket server = null;
        try {
            //1、创建socket连接
            server = new ServerSocket(port);
            //2、获取所有服务类
//            Map services = getService(clazz);


            //3、创建线程池

            Executor executor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() {
            });
            while(true){
                //4、获取客户端连接
                Socket client = server.accept();
                //5、将服务端被调用的服务放到线程池中异步执行
                RpcProviderHandler service = new RpcProviderHandler(client,services);
                executor.execute(service);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (server!=null){
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}


public class RpcProviderHandler implements Runnable {
    private Socket clientSocket;
    private Map<String,Object> serviceMap;
    public RpcProviderHandler(Socket client, Map<String, Object> services) {
        this.clientSocket = client;
        this.serviceMap = services;
    }

    @Override
    public void run() {
        ObjectInputStream ois = null;
        ObjectOutputStream oos = null;
        RpcResponse response = new RpcResponse();
        try{
            ois = new ObjectInputStream(clientSocket.getInputStream());
            oos = new ObjectOutputStream(clientSocket.getOutputStream());

            //反序列化
            Object object = ois.readObject();
            Invocation invocation = invocation = (Invocation) object;


            //查找并执行服务
            Class<?> clazz = (Class<?>) serviceMap.get(invocation.getClassName());
            Method method = clazz.getMethod(invocation.getMethodName(),invocation.getParamTypes());
            Object result = method.invoke(clazz.newInstance(),invocation.getParams());

            response.setResult(result);
            oos.writeObject(response);
            oos.flush();

        } catch (Exception  e) {
            if (oos != null) {
                response.setError(e);
                try {
                    oos.writeObject(response);
                    oos.flush();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        } finally {
            try {
            if (ois!=null) ois.close();
            if (oos != null) oos.close();
            if (clientSocket != null) clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

一个客户端代理类,可以不用在代码中写死方法名、参数类型等:

public class RpcProviderProxy {
    Logger log= LoggerFactory.getLogger(RpcProviderProxy.class);
    private URL url;

    public RpcProviderProxy(URL url) {
        this.url=url;
    }

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz){
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                log.info("可以做一些执行方法的前置处理");
                Invocation invocation = new Invocation();
                invocation.setClassName(method.getDeclaringClass().getName());
                invocation.setMethodName(method.getName());
                invocation.setParamTypes(method.getParameterTypes());
                invocation.setParams(args);
                Object result = new RpcConsumer().execute(invocation, url.getHostname(), url.getPort());
                //功能增强,比如记录流水信息
                log.info("反射中的方法执行完毕,返回结果为:"+result);
                log.info("可以做一些执行方法的后置处理");
                return result;
            }
        });
    }

}

OK,这时我们只需要再来个客户端测试类、服务端测试类即可:

public class ConsumerMain {
    public static void main(String[] args) {
        URL url=new URL("127.0.0.1", 7777);
        RpcProviderProxy clientProxy = new RpcProviderProxy(url);

        //代理类
        DemoService proxy = clientProxy.getProxy(DemoService.class);
        System.out.println(proxy.sendAndGetMessage("测试发送信息"));
    }
}

public class ProviderMain {
    public static void main(String[] args) {
        Map<String,Object> services = new HashMap<>();
        services.put(DemoService.class.getName(), DemoServiceImpl.class);

        RpcProvider server= new RpcProvider();
        server.start(7777,services);
    }
}
public class DemoServiceImpl implements DemoService {
    Logger log=LoggerFactory.getLogger(DemoServiceImpl.class);
    @Override
    public String sendAndGetMessage(String message) {
        log.info("执行了客户端的方法,参数为:"+message);
        return message;
    }
}

这样,我们就完成了一个简单的RPC了,run一下看下效果怎么样,记得先启动服务端(ProviderMain),再启动客户端(ConsumerMain)

手把手教你手写一个RPC框架_第5张图片
在这里插入图片描述

Ok,完美。这个例子直接复制上面的代码即可运行。至于SPI、注册中心等,我们在下一篇中再进行实现,可能又要和之前解析Springboot启动流程那样来个万字长文了。

你可能感兴趣的:(中间件,dubbo,rpc)