RPC核心原理及代码演示


本文以极简的Java代码演示RPC框架的基本原理。

需求描述:
1.客户端调用远程服务ProductService、UserSerivce的接口,并打印结果
2.服务端提供具体的服务实现类,接受客户端请求,并返回响应
3.客户端像使用本地方法一样调用远程接口,对网络通信、序列化等细节无感知

废话少说,直接上代码!

远程接口定义

public interface IProductService {
    public Product findProductById(Integer id);
}
public interface IUserSerivce {
    public User findUserById(Integer id);
}
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String name;

    public Product(Integer id, String name) {
        this.name = name;
        this.id = id;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
public class User implements Serializable {
    private static final long serialVersionUID = 2L;
    private Integer id;
    private String name;

    public User(Integer id, String name) {
        this.name = name;
        this.id = id;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

客户端代码

服务调用者( RPC客户端)获取服务提供方(RPC服务端)提供的服务,并像调用本地接口一样调用远程方法。

public class Client {
    public static void main(String[] args) {
        //客户端指定需要调用的远程服务(客户端可以注入服务提供方的服务)、调用服务的接口方法
        IProductService productService = (IProductService) Stub.getstub(IProductService.class);
        System.out.println(productService.findProductById(1));

        IUserSerivce userSerivce = (IUserSerivce) Stub.getstub(IUserSerivce.class);
        System.out.println(userSerivce.findUserById(123));
    }
}

/**
 * Stub用于代理服务端的方法,对服务调用者(客户端)屏蔽socket连接、序列化等细节
 */

public class Stub {
    public static Object getstub(Class clazz) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //模拟服务发现,直接socket连接到服务提供者的机器上,把要调用的服务名和方法名、方法参数传到服务端,服务端收到后解析请求并执行方法调用返回数据
                Socket socket = new Socket("127.0.0.1", 9999);
                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;
            }
        };

        Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);
        return o;
    }
}

服务端代码

//具体的服务实现类
public class ProductServiceImpl implements IProductService {
    @Override
    public Product findProductById(Integer id) {
        return new Product(id, "productA");
    }
}

public class UserServiceImpl implements IUserSerivce {
    @Override
    public User findUserById(Integer id) {
        return new User(id, "UserA");
    }
}
/**
 * 服务端不断接受客户端请求,按照双方约定的格式(协议)反序列化服务端发来的消息、完成服务端本地接口调用、返回序列化数据
 */
public class Server {
    private static boolean running = true;

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

    private static void process(Socket socket) throws Exception {
        InputStream inputStream = socket.getInputStream();
        OutputStream outputStream = socket.getOutputStream();
        ObjectInputStream ois = new ObjectInputStream(inputStream);
        //按照服务端写入的格式来反序列化数据
        String clazzName = ois.readUTF();
        String methodName = ois.readUTF();
        Class<?>[] parameterTypes = (Class<?>[]) ois.readObject();
        Object[] args = (Object[]) ois.readObject();

        //模拟从服务注册表查找到具体的服务实现类
        Class clazz = findService(clazzName);
        Method method = clazz.getMethod(methodName, parameterTypes);
        Object o = method.invoke(clazz.newInstance(), args);
        ObjectOutputStream oos = new ObjectOutputStream(outputStream);
        oos.writeObject(o);
        oos.flush();
    }

    private static Class findService(String clazzName) throws Exception {
        if (IProductService.class.getName().equals(clazzName)) {
            return ProductServiceImpl.class;
        } else if (IUserSerivce.class.getName().equals(clazzName)) {
            return UserServiceImpl.class;
        } else {
            throw new Exception("no service found");
        }
    }
}

运行演示

先运行Server、再运行Client
在这里插入图片描述

一次完整的RPC调用流程如下:
1)服务消费方(client)调用以本地调用方式调用服务;
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息实体;
3)client stub找到服务地址,并将消息发送到服务方;
4)服务提供方server 端收到消息后进行解码;
5)server 端根据解码结果调用本地接口;
6)server 端将本地接口调用结果组装成消息并发送至消费方;
7)client stub接收到消息,并进行解码、反序列化;
8)服务消费方得到最终结果。

序列化方案

我们都知道jdk自带的序列化性能一般,市面上有很多可替代的序列化方案,比如Google的proto buffer、Hessian、Thrift等。

下面我们就选择其中的一种,来作为我们这个RPC demo的 序列化层,废话少说直接上代码:

更多内容请关注【程猿薇茑】

欢迎关注公众号「程猿薇茑」
**微信扫一扫**
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程猿薇茑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值