手写RPC框架

57 阅读4分钟

简单概念

RPC全程是(Remote Procedure Call)远程过程调用,使得分布式应用更加容易构建。一个应用调用一个远程功能时,像是在本地调用一样。因此RPC需要提供一中透明调度机制,让本地应用不必显示的区分本地调用和远程调用。

开源RPC框架

  • Dubbo 阿里巴巴于2011年开源,仅支持Java
  • Motan 微博内部使用,仅支持java
  • springCloud,spring全家桶里面的组建,提供了一个丰富的spring生态
  • gRPC Google 2015年开源,支持跨语言,多种语言
  • Thrift Facebook开源,贡献给了 Apache基金,支持多种语言

特点

  • RPC框架一般使用长连接,不必每次通信都3次握手,减少开销
  • RPC一般都有注册中心、监控管理。服务提供者发布、下线接口、动态扩展,调用方无感知的

代码

主要包含四部分

  • 把服务调用者称为Consumer
  • 把提供服务方称为Provider
  • 把服务注册中心称为RPC
  • 把透明接口称为Provider-Consumer

透明接口

这里消费者要调用的方法是HelloService

public interface HelloService {
    public String sayHello(String name) ;
}

Consumer

public class Consumer {
    public static void main(String[] args) {
        Invocation invocation = new Invocation(HelloService.class.getName(),"sayHello",
                new Class[]{String.class}, new Object[]{"程小青"});

        HttpClient httpClient = new HttpClient();
        String result = httpClient.send("localhost",8080, invocation);
        System.out.println(result);
    }
}

Provider

Provider启动,向注册中心注册服务,然后启动后等待消费者的连接

public class Provider {
    public static void main(String[] args) {
        // 向注册中心注册服务
        LocalRegister.regist(HelloService.class.getName(), HelloServiceImpl.class);
        // Netty, Tomcat
        HttpServer httpServer  =new HttpServer();
        httpServer.start("localhost", 8080);
    }
}

Provider所提供的服务,要实现透明接口

public class HelloServiceImpl implements HelloService{
    @Override
    public String sayHello(String name) {
        return "hello: " + name;
    }
}

本地注册中心

  • Invocation规定了请求的格式
  • DispatcherServlet负责将收到的请求转交给相应的Handler进行处理
  • LocalRegister 是服务注册中心

image.png

Invocation

请求的格式,在调用远程服务时,我们要指定所调用的服务的接口名、方法名、方法的参数类型列表(便于同名方法的重载)、方法的参数列表。

// 因为是网络请求的参数,所以要支持序列化,这里用的是JDK的序列化,也要实现其他的比如用JSON的序列化方式等
public class Invocation implements Serializable {
    //调用的接口名
    private String interfaceName;
    //调用的方法名
    private String methodName;
    //调用的参数类型,判断同名方法的重载
    private Class[] parameterTypes;
    //调用的参数值
    private Object[] parameters;

    public Invocation(String interfaceName, String methodName, Class[] parameterTypes, Object[] parameters) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.parameterTypes = parameterTypes;
        this.parameters = parameters;
    }
    ... 省略Get Set方法
DispatcherServlet

相当于一个请求中转站,会根据受到的请求,分发给相应的处理器Handler进行处理。

/**
 * DispatcherServlet相当于一个中转站
 */
public class DispatcherServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 拿到请求之后,交给相应的处理器去处理,(在一些框架里,handler通常是过滤器和责任链的模式)
        new HttpServerHandler().handler(req, resp);
    }
}
HttpServerHandler

处理请求的组件。

/**
 * 专门处理请求的
 */
public class HttpServerHandler {
    public void handler(HttpServletRequest req, HttpServletResponse resp){
        // 处理请求 --->调用哪个接口的哪个方法,传的什么参数(接口、方法、方法参数,可以把这三者抽象成一个对象)
        //首先要判断序列化的方式然后进行反序列化,这里直接使用了JDK的反序列化
        try {
            Invocation invocation = (Invocation) new ObjectInputStream(req.getInputStream()).readObject();
            //获取到接口名字
            String interfaceName = invocation.getInterfaceName();
            //从注册中心找到对应的接口实现类
            Class classImpl = LocalRegister.get(interfaceName);
            // 根据实现类、方法名字、参数类型找到对应的方法
            Method method = classImpl.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
            //执行方法
            Object result = method.invoke(classImpl.newInstance(), invocation.getParameters());
            // 把结果写入Response中
            IOUtils.write((String)result,resp.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}
HttpServer,HttpClient

这两个组件分别是为Provider和Consumer发起网络请求准备的。

LocalRegister

本地服务注册

/**
 * 本地注册,当然还可以扩展为注册中心注册
 */
public class LocalRegister {
    // 接口名字对应的类
    private static Map<String, Class> map = new HashMap<>();

    /**
     * 注册接口,接口名字对应哪个实现类
     * @param interfaceName
     * @param implClass
     */
    public static void regist(String interfaceName, Class implClass) {
        map.put(interfaceName, implClass);
    }

    /**
     * 根据接口名字返回接口
     * @param interfaceName
     * @return
     */
    public static Class get(String interfaceName){
        return map.get(interfaceName);
    }
}

详细的RPC介绍可参考:zhuanlan.zhihu.com/p/374901408 本Demo地址:github.com/zljya/JAVAR…