RPC(一):RPC详解

目录

1、RPC代码分析1

2、RPC代码分析2

3、RPC代码分析3

4、RPC代码分析4

5、RPC代码分析5

6、RPC代码分析6

7、RPC序列化框架

8、Hessian

参考:https://blog.csdn.net/ss123mlk/article/details/108555850

RPC组成

TCP/IP模拟RPC

01 最基础二进制传递

02 简化客户端的流程 引入stub(客户端存根)

03 使用动态代理生成service类供客户端调用 彻底隐藏所有网络细节

04 引入客户端存根的真正含义-支持多个方法的打包 服务端利用反射解析打包过来的消息并invoke执行

05 服务端支持不同参数的函数的返回结果

06 服务端支持对不同类的不同函数、不同参数的结果返回

总结


从单机都分布式 即分布式通信最基本的是二进制书的TCP/IP传输。

1、RPC代码分析1

实现功能:根据id查询出用户实体。

1、实体类User

@Data
@AllArgsConstructor
public class User implements Serializable {

    private static final long serialVersionUID = 1L;
    private Integer id;
    private String name;
}

2、服务端

通过数据流读出客户端的数据。

将对象通过输出流dos写出到客户端。

public class Server {
    private static boolean running = true;
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);
        while(running){
            Socket s = ss.accept();
            process(s);
            s.close();
        }
        ss.close();
    }

    private static void process(Socket s) throws IOException {
        InputStream is = s.getInputStream();
        OutputStream os = s.getOutputStream();
        DataInputStream dis = new DataInputStream(is);
        DataOutputStream dos = new DataOutputStream(os);

        int id = dis.readInt();
        IUserService userService = new UserServiceImpl();
        User user = userService.findUserById(id);

        dos.writeInt(user.getId());
        dos.writeUTF(user.getName());
        dos.flush();
    }
}

3、客户端

客户端需要将数据转为二进制才能进行网络传输,故使用ByteArrayOutputStream。通过dos.write(123)将整数123写入baos输出流中,通过baos.toByteArray()将整数转为字节数组。

再通过输入流读入服务端的数据。

public class Client {
    /**
     * 客户端先将数据写入DataOutputStream中,DataOutputStream包装在
     * ByteArrayOutputStream的外边,最终写出的是ByteArrayOutputStream。
     **/
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        dos.write(123);
        socket.getOutputStream().write(baos.toByteArray());
        socket.getOutputStream().flush();

        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        int id = dis.readInt();
        String name = dis.readUTF();
        User user = new User(id, name);
        System.out.println(user);

        dos.close();
        socket.close();
    }
}

4、Service层

public interface IUserService {
    public User findUserById(Integer id);
}

public class UserServiceImpl implements IUserService{
    @Override
    public User findUserById(Integer id) {
        return new User(id,"Alice");
    }
}

2、RPC代码分析2

1、Stub代理类
   Stub就是一个代理类,封装了socket底层。开发者只需要关系业务逻辑即可。 问题:此时代理类Stub只能代理一个方法,返回一个对象。
/**
 * Stub就是一个代理类,封装了socket底层。开发者只需要关系业务逻辑即可
 * 问题:此时代理类Stub只能代理一个方法,返回一个对象。
 **/
public class Stub {

    public User findUserById(Integer id) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        dos.write(123);
        socket.getOutputStream().write(baos.toByteArray());
        socket.getOutputStream().flush();

        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        int receivedId = dis.readInt();
        String name = dis.readUTF();
        User user = new User(receivedId, name);

        dos.close();
        socket.close();
        return user;
    }
}

2、客户端Client

public class Client {
    public static void main(String[] args) throws IOException {
        Stub stub = new Stub();
        User user = stub.findUserById(123);
    }
}

3、服务端Server

public class Server {
    private static boolean running = true;
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);
        while(running){
            Socket s = ss.accept();
            process(s);
            s.close();
        }
        ss.close();
    }

    private static void process(Socket s) throws IOException {
        InputStream is = s.getInputStream();
        OutputStream os = s.getOutputStream();
        DataInputStream dis = new DataInputStream(is);
        DataOutputStream dos = new DataOutputStream(os);

        int id = dis.readInt();
        IUserService userService = new UserServiceImpl();
        User user = userService.findUserById(id);

        dos.writeInt(user.getId());
        dos.writeUTF(user.getName());
        dos.flush();

    }
}

3、RPC代码分析3

动态代理:现有一个含有方法的接口IUserService,现要动态产生一个该接口的对象,而该对象称为代理对象。在调用代理对象的方法时,会自动调用InnocationHandler的Invoke方法。

invoke()参数是1、代理的对象;2、调用的方法;3、方法的参数。

1、客户端

客户端需求就是根据id获取User实体,而如果能在客户端直接调用service.findUserById()就更好了。

实现:可以通过动态代理在客户端产生一个IUserService的代理对象,在调用代理对象的findUserById()方法时,会自动调用InvocationHandler【调用处理器】的invoke方法,再该方法的参数中含有【代理对象,实现的方法,方法的参数】,即可以实现在 调用代理对象时进行一些操作。。。

public class Client {
    public static void main(String[] args) {
        IUserService service = Stub.getStub();
        //在findUserById内部加入了网络细节
        User userById = service.findUserById(123);
        System.out.println(userById);
    }
}

2、代理对象

/**
 * 代理模式 -- 动态代理【rpc核心之一】
 * 通过动态代理,动态产生类
 **/
public class Stub {

    /**
     * 动态产生了,一个实现接口的实现类,当调用实现类内所有方法时,都执行invoke方法。
     *
     * InvocationHandler调用处理器
     **/
    public static IUserService getStub(){
        InvocationHandler h = new InvocationHandler(){

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket("127.0.0.1", 8888);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                DataOutputStream dos = new DataOutputStream(baos);
                dos.write(123);
                socket.getOutputStream().write(baos.toByteArray());
                socket.getOutputStream().flush();

                InputStream is = socket.getInputStream();
                DataInputStream dis = new DataInputStream(is);
                int receivedId = dis.readInt();
                String name = dis.readUTF();
                User user = new User(receivedId, name);

                dos.close();
                socket.close();
                return user;
            }
        };
        //new代理类对象【参数2是代理类实现的接口】
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
        //证明是动态产生的类  com.sun.proxy.$Proxy0
        System.out.println(o.getClass().getName());
        //interface rpc.test.common.IUserService
        System.out.println(o.getClass().getInterfaces()[0]);
        return (IUserService)o;
    }
}

3、服务端

public class Server {
    private static boolean running = true;

    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);
        while(running){
            Socket s = ss.accept();
            process(s);
            s.close();
        }
        ss.close();
    }

    private static void process(Socket s) throws IOException {
        InputStream is = s.getInputStream();
        OutputStream os = s.getOutputStream();
        DataInputStream dis = new DataInputStream(is);
        DataOutputStream dos = new DataOutputStream(os);

        int id = dis.readInt();
        IUserService userService = new UserServiceImpl();
        User user = userService.findUserById(id);

        dos.writeInt(user.getId());
        dos.writeUTF(user.getName());
        dos.flush();
    }
}

4、RPC代码分析4

版本3的问题:动态代理的实现只能调用findUserById()方法,并且参数id写死了也不够灵活。如果接口中有其他方法则不能调用。

实现思路:客户端获取具体需要调用的方法名【saveUser(),findUserById】和方法参数【避免方法重载】并将其传给服务端。

1、客户端

public class Client {
    public static void main(String[] args) {
        IUserService service = Stub.getStub();
        //在findUserById内部加入了网络细节
        User userById = service.findUserById(123);
        System.out.println(userById);
    }
}

2、代理对象

/**
 * 代理模式 -- 动态代理【rpc核心之一】
 * 通过动态代理,动态产生类
 **/
public class Stub {
    /**
     * 动态产生了,一个实现接口的实现类,当调用实现类内所有方法时,都执行invoke方法。
     * InvocationHandler调用处理器
     **/
    public static IUserService getStub(){
        InvocationHandler h = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket("127.0.0.1", 8888);
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                //方法名、参数类型【防止重载】、参数
                String methodName = method.getName();
                Class[] parameterTypes = method.getParameterTypes();
                oos.writeUTF(methodName);
                oos.writeObject(parameterTypes);
                oos.writeObject(args);
                oos.flush();

                InputStream is = socket.getInputStream();
                DataInputStream dis = new DataInputStream(is);
                int receivedId = dis.readInt();
                String name = dis.readUTF();
                User user = new User(receivedId, name);

                oos.close();
                socket.close();
                return user;
            }
        };
        //new代理类对象【参数2是代理类实现的接口】
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
        return (IUserService)o;
    }
}

3、服务端

public class Server {
    private static boolean running = true;

    public static void main(String[] args) throws Exception {
        ServerSocket ss = new ServerSocket(8888);
        while(running){
            Socket s = ss.accept();
            process(s);
            s.close();
        }
        ss.close();
    }

    private static void process(Socket s) throws Exception {
        InputStream is = s.getInputStream();
        OutputStream os = s.getOutputStream();
        ObjectInputStream oos = new ObjectInputStream(is);
        DataOutputStream dos = new DataOutputStream(os);

        String methodName = oos.readUTF();
        Class[] parameterTypes = (Class[])oos.readObject();
        Object[] args = (Object[])oos.readObject();

        IUserService service = new UserServiceImpl();
        Method method = service.getClass().getMethod(methodName, parameterTypes);
        User user = (User)method.invoke(service, args);

        dos.writeInt(user.getId());
        dos.writeUTF(user.getName());
        dos.flush();
    }
}

5、RPC代码分析5

版本4的问题:服务端中返回值到客户端时,将User拆分后返回,不如很直接返回User对象更好。

实现:返回值用Object封装,支持任意类型。

1、客户端

public class Client {
    public static void main(String[] args) {
        IUserService service = Stub.getStub();
        //在findUserById内部加入了网络细节
        User userById = service.findUserById(123);
        System.out.println(userById);
    }
}

2、代理对象

/**
 * 代理模式 -- 动态代理【rpc核心之一】
 * 通过动态代理,动态产生类
 **/
public class Stub {
    /**
     * 动态产生了,一个实现接口的实现类,当调用实现类内所有方法时,都执行invoke方法。
     * InvocationHandler调用处理器
     **/
    public static IUserService getStub(){
        InvocationHandler h = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket("127.0.0.1", 8888);
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                //方法名、参数类型【防止重载】、参数
                String methodName = method.getName();
                Class[] parameterTypes = method.getParameterTypes();
                oos.writeUTF(methodName);
                oos.writeObject(parameterTypes);
                oos.writeObject(args);
                oos.flush();

                InputStream is = socket.getInputStream();
                DataInputStream dis = new DataInputStream(is);
                int receivedId = dis.readInt();
                String name = dis.readUTF();
                User user = new User(receivedId, name);

                oos.close();
                socket.close();
                return user;
            }
        };
        //new代理类对象【参数2是代理类实现的接口】
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
        return (IUserService)o;
    }
}

3、服务端

public class Server {
    private static boolean running = true;

    public static void main(String[] args) throws Exception {
        ServerSocket ss = new ServerSocket(8888);
        while(running){
            Socket s = ss.accept();
            process(s);
            s.close();
        }
        ss.close();
    }

    private static void process(Socket s) throws Exception {
        InputStream is = s.getInputStream();
        OutputStream os = s.getOutputStream();
        ObjectInputStream ois = new ObjectInputStream(is);
        DataOutputStream dos = new DataOutputStream(os);

        String methodName = ois.readUTF();
        Class[] parameterTypes = (Class[])ois.readObject();
        Object[] args = (Object[])ois.readObject();

        IUserService service = new UserServiceImpl();
        Method method = service.getClass().getMethod(methodName, parameterTypes);
        User user = (User)method.invoke(service, args);

        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(user);
        dos.flush();
    }
}

6、RPC代码分析6

版本5的问题:客户端中IUserService service = Stub.getStub();表示只能够得到IUserService接口,不能得到其他接口

实现:通过传入xxx.class,得到对应的Object

1、服务端

public class Client {
    public static void main(String[] args) {
        IProductService service = (IProductService) Stub.getStub(IUserService.class);
        System.out.println(service.findProductById(321));
    }
}

2、新实体

public interface IProductService {
    public Product findProductById(Integer id);
}
public class ProductServiceImpl implements IProductService{
    @Override
    public Product findProductById(Integer id) {
        return new Product(id,"Alice");
    }
}
@Data
@AllArgsConstructor
public class Product implements Serializable {

    private static final long serialVersionUID = 1L;
    private Integer id;
    private String name;
}

3、stub

public class Stub {
    /**
     * 动态产生了,一个实现接口的实现类,当调用实现类内所有方法时,都执行invoke方法。
     * InvocationHandler调用处理器
     **/
    public static Object getStub(Class clazz){
        InvocationHandler h = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket("127.0.0.1", 8888);
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                //类名、方法名、参数类型【防止重载】、参数
                String clazzName= clazz.getName();
                String methodName = method.getName();
                Class[] parameterTypes = method.getParameterTypes();
                oos.writeUTF(clazzName);
                oos.writeUTF(methodName);
                oos.writeObject(parameterTypes);
                oos.writeObject(args);
                oos.flush();

                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Object o = ois.readObject();

                oos.close();
                socket.close();
                return o;
            }
        };
        //new代理类对象【参数2是代理类实现的接口】
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
        return o;
    }
}

4、服务端

public class Server {
    private static boolean running = true;

    public static void main(String[] args) throws Exception {
        ServerSocket ss = new ServerSocket(8888);
        while(running){
            Socket s = ss.accept();
            process(s);
            s.close();
        }
        ss.close();
    }

    private static void process(Socket s) throws Exception {
        InputStream is = s.getInputStream();
        OutputStream os = s.getOutputStream();
        ObjectInputStream ois = new ObjectInputStream(is);
        DataOutputStream dos = new DataOutputStream(os);

        String clazzName = ois.readUTF();
        String methodName = ois.readUTF();
        Class[] parameterTypes = (Class[])ois.readObject();
        Object[] args = (Object[])ois.readObject();

        Class clazz = null;
        //从服务注册表找到具体的类
        clazz = UserServiceImpl.class;

        Method method = clazz.getMethod(methodName, parameterTypes);
        Object o = (Object)method.invoke(clazz.newInstance(), args);

        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(o);
        dos.flush();
    }
}

7、RPC序列化框架

1、java.io.Serializable【jdk效率低】

2、Hessian

3、google protobuf

4、facebook Thrift

5、kyro

6、fst

7、json序列化框架【jaskson】google Gson、Ali FastJson】

8、xmlrpc(xstream)

...

8、Hessian

参考:https://blog.csdn.net/ss123mlk/article/details/108555850

RPC组成

一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等。
RPC(一):RPC详解_第1张图片

  • 下面分别介绍核心 RPC 框架的重要组成:
    客户端(Client):服务调用方。
    客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
    服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
    服务端(Server):服务的真正提供者。
    Network Service:底层传输,可以是 TCP 或 HTTP。





TCP/IP模拟RPC

由服务的调用方与服务的提供方建立 Socket 连接,并由服务的调用方通过 Socket 将需要调用的接口名称、方法名称和参数序列化后传递给服务的提供方,服务的提供方反序列化后再利用反射调用相关的方法。

***将结果返回给服务的调用方,整个基于 TCP 协议的 RPC 调用大致如此。





01 最基础二进制传递

RPC(一):RPC详解_第2张图片





02 简化客户端的流程 引入stub(客户端存根)

RPC(一):RPC详解_第3张图片



客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。

RPC(一):RPC详解_第4张图片





03 使用动态代理生成service类供客户端调用 彻底隐藏所有网络细节

  • 在02的client中 用stub调用方法 底层暴露了 希望用封装的Service调用 而且隐藏底层细节
    于是引入动态代理 生成service类给client使用 在stub中也不用写每一个业务函数的细节
  • 在02的stub中 随着业务的增多方法会越来越多 那就会堆积很多函数 需要反射来使手写的函数减少
    当外部使用返回的动态代理对象去调用某个功能的时候 内部就会method反射

RPC(一):RPC详解_第5张图片
RPC(一):RPC详解_第6张图片
RPC(一):RPC详解_第7张图片
RPC(一):RPC详解_第8张图片





04 引入客户端存根的真正含义-支持多个方法的打包 服务端利用反射解析打包过来的消息并invoke执行

client还是调用希望调用的函数 stub却对函数进行了规则化的传递 不在在自己这里处理了
RPC(一):RPC详解_第9张图片
server:
RPC(一):RPC详解_第10张图片
现在支持不同功能的函数经过存根打包 在网络中传输给服务端 那服务端也要对相应的服务进行解析并且返回函数





05 服务端支持不同参数的函数的返回结果

服务端写回一个对象 而不是在客户端哪里获得属性然后组装对象
RPC(一):RPC详解_第11张图片

在客户端存根当中 服务端写回的结果进行了改变
RPC(一):RPC详解_第12张图片





06 服务端支持对不同类的不同函数、不同参数的结果返回

存根就是网络消息打包存放的地方
现在已经升级为通过动态代理 支持所有的类的所有的函数并且根据类型和参数能够区别重写的函数
返回结果也支持所有的类型
目前就是客户端做调用 然后存根进行打包运输给服务端
服务端解析并且返回对象 存根再将对象写回
RPC(一):RPC详解_第13张图片
RPC(一):RPC详解_第14张图片
server 获取名字
RPC(一):RPC详解_第15张图片





总结

一次 RPC 调用流程如下:

服务消费者(Client 客户端)通过本地调用的方式调用服务。
客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。
客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端。
服务端存根(Server Stub)收到消息后进行解码(反序列化操作)。
服务端存根(Server Stub)根据解码结果调用本地的服务进行相关处理
服务端(Server)本地服务业务处理。
处理结果返回给服务端存根(Server Stub)。
服务端存根(Server Stub)序列化结果。
服务端存根(Server Stub)将结果通过网络发送至消费方。
客户端存根(Client Stub)接收到消息,并进行解码(反序列化)。
服务消费方得到最终结果


马老师讲的是本机的rpc调用 在微服务框架中微服务应用都是在不同的JVM、不同的内存中
这时候简单的动态代理和反射就可能调用到其他微服务的内存空间里了 甚至会造成并发的一个错乱
所以就引入了 相关的注册中心(Eureka zookeeper) 服务发现 根据ID

  • 在 RPC 中,所有的函数都必须有自己的一个 ID。这个 ID 在所有进程中都是唯一确定的。客户端和服务端分别维护一个函数和Call ID的对应表。
  • 客户端想要调用函数A 就查找自己所的对应表把A对应的ID通过存根传输 服务端根据ID在自己这边的表中找到函数 并执行
  • 网络传输层需要把 Call ID 和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。

你可能感兴趣的:(HTTP/RESTful,RPC/Thrift,java)