Java讲解RPC的基本实现

本文涉及的产品
系统运维管理,不限时长
简介:

Java讲解RPC的基本实现

RPC远程过程调用可以说是分布式系统的基础,本文将通过Java演示一次普通的rpc调用到底发生了什么。

我曾经在网上看到有人提问,为什么RPC要叫作远程过程调用,而不叫作RMC远程方法调用。个人认为RPC的叫法才是合理的,远程调用的是某个过程,不一定是一个具体的方法,你只要看过第一个版本的代码就能懂了。

这整个过程可以用一句话概括:机器A通过网络与机器B建立连接,A发送一些参数给B,B执行某个过程,并把结果返回给A。

先说一个前置背景,我们有一个商品类

public class Product implements Serializable {

private Integer id;
private String name;

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

//toString()

//get set 方法

}
有一个商品服务接口

public interface IProductService {

Product getProductById(Integer id);

}
服务端有商品服务接口的实现类

public class ProductServiceImpl implements IProductService {

@Override
public Product getProductById(Integer id) {
    //实际上这里应该去查询数据库获得数据,下面简化了
    return new Product(id, "手机");
}

}
下面我们通过客户端发送一个商品id到服务端,服务端获得id后通过通过商品服务类获取商品信息,返回给客户端

public class Client {

public static void main(String[] args) throws Exception {
    //建立Socket
    Socket socket = new Socket("127.0.0.1", 8888);
    //获取输出流
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    //把商品Id通过网络传到服务端
    dos.writeInt(123);

    socket.getOutputStream().write(baos.toByteArray());
    socket.getOutputStream().flush();

    //读取服务端返回的商品信息
    DataInputStream dis = new DataInputStream(socket.getInputStream());
    Integer id = dis.readInt();     //商品id
    String name = dis.readUTF();    //商品名称
    Product product = new Product(id, name);//通过服务端返回的商品信息生成商品

    System.out.println(product);
    
    //关闭流资源为了方便阅读,没有做try-catch处理
    dos.close();
    baos.close();
    socket.close();
}

}

public class Server {

private static boolean running = true;

public static void main(String[] args) throws Exception {
    //建立服务端Socket
    ServerSocket ss = new ServerSocket(8888);
    //不断监听,处理客户端请求
    while (running) {
        Socket socket = ss.accept();
        process(socket);
        socket.close();
    }
    ss.close();
}

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

    //读取客户端发过来的id
    Integer id = dis.readInt();
    //调用服务类生成商品
    IProductService service = new ProductServiceImpl();
    Product product = service.getProductById(id);
    //把商品的信息写回给客户端
    dos.writeInt(id);
    dos.writeUTF(product.getName());
    dos.flush();

    dos.close();
    dis.close();
    os.close();
    is.close();
}

}
上面的是RPC远程调用1.0版本,可以看到联网的代码写死在了客户端中,网络的代码和getProductById()耦合在了一起,实际的rpc框架是绝对不可能这么做的。

在实际的使用中,我们会编写各种各样的远程调用,打个比方,IProductService接口以后可能会扩展成这样:

public interface IProductService {

Product getProductById(Integer id);

Product getProductByName(String name);

Product getMostExpensiveProduct();

}
我们总不可能为每个方法都编写一段网络连接的代码吧,我们得想到一种办法为所有的方法都嵌入一段共用的网络连接代码。

那具体应该怎样嵌入呢?这里我们可以用到代理模式。

在Java中许多优秀的框架都用到了代理模式做代码嵌入,比如说Mybatis。它把JDBC连接部分的代码通过代理模式嵌入到sql语句的周围,让我们专注于写sql。

首先,服务端的代码要进行修改:

public class Server {

private static boolean running = true;

public static void main(String[] args) throws Exception {
    //......
}

private static void process(Socket socket) throws Exception {
    //获取输入流,输出流
    InputStream is = socket.getInputStream();
    OutputStream os = socket.getOutputStream();
    ObjectInputStream ois = new ObjectInputStream(is);
    ObjectOutputStream oos = new ObjectOutputStream(os);
    //获取本次远程调用的方法名
    String methodName = ois.readUTF();
    //获取本次远程调用方法的参数类型
    Class[] parameterTypes = (Class[]) ois.readObject();
    //获取具体的参数对象
    Object[] args = (Object[]) ois.readObject();
    
    //创建商品服务类实例
    IProductService service = new ProductServiceImpl();
    //根据远程获取的方法名和参数,调用相应的方法
    Method method = service.getClass().getMethod(methodName, parameterTypes);
    Product product = (Product) method.invoke(service, args);
    //把结果写回给客户端
    oos.writeObject(product);

    oos.close();
    ois.close();
    socket.close();
}

}
然后在客户端,我们创建一个新的代理类,对外提供一个getStub获取代理类的方法。使用JDK的动态代理需要三个参数,一个是类加载器,一个是接口的class类,最后一个是InvocationHandler实例。

JDK动态代理背后的逻辑是这样的:JVM会根据接口的class类动态创建一个代理类对象,这个代理对象实现了传入的接口,也就是说它拥有了接口中所有方法的实现。方法具体的实现可以由用户指定,也就是调用InvocationHandler的invoke方法。

在invoke方法中有三个参数,分别是proxy代理类,method调用的方法,args调用方法的参数。我们可以在invoke方法中对具体的实现方法进行增强,在本案例中就是进行网络调用。

public class Stub {

public static IProductService getStub() {

    InvocationHandler h = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //和服务端建立Socket连接
            Socket socket = new Socket("127.0.0.1", 8888);
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            //拿到远程调用的方法名
            String methodName = method.getName();
            //拿到远程调用方法的参数类型
            Class[] parametersTypes = method.getParameterTypes();
            //把方法名传递给服务端
            oos.writeUTF(methodName);
            //把方法参数类型传递给服务端
            oos.writeObject(parametersTypes);
            //把方法参数传递给服务端
            oos.writeObject(args);
            oos.flush();
            //获取远程调用的返回结果
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Product product = (Product) ois.readObject();

            ois.close();
            oos.close();
            socket.close();
            return product;
        }
    };
    Object o = Proxy.newProxyInstance(IProductService.class.getClassLoader(), new Class[]{IProductService.class}, h);
    return (IProductService) o;
}

}
这个新版本比第一个版本又美好了一些,但是其实还可以继续优化。现在我们的代理只能够返回IProductService的实现类,得想办法让它返回任意类型的服务实现类。

思路和远程调用方法相似,在远程调用方法时,我们把方法的名称,参数类型,参数传递给服务端;现在要动态创建服务类,我们可以把服务接口的名字传给服务端。服务端拿到远程接口的名字后,就可以从服务注册表中找到对应服务实现类。

至于服务实现类如何注册到服务注册表,这里提供一个思路:可以考虑使用Spring的注解注入。这和我们平时写spring代码是相似的,在创建完服务实现类后我们会加上注解@Service,这样我们就可以在收到远程调用后,遍历使用了@Service的Bean,找到对应的实现类。

参考:马士兵rpc的演化过程公开课
https://www.bilibili.com/video/BV1Ug4y1875i?p=2

原文地址https://www.cnblogs.com/tanshaoshenghao/p/12767414.html

相关文章
|
6月前
|
Java Android开发
java利用xml-rpc协议操作wordpress博客
java利用xml-rpc协议操作wordpress博客
85 1
|
4月前
|
负载均衡 Java
使用Java实现RPC框架
使用Java实现RPC框架
|
4月前
|
负载均衡 Java
|
6月前
|
XML JSON Java
RPC框架之Thrift—实现Go和Java远程过程调用
RPC框架之Thrift—实现Go和Java远程过程调用
138 1
|
Java Apache 数据安全/隐私保护
Java RPC调用: 远程过程调用的实现与应用
Java RPC调用: 远程过程调用的实现与应用
|
Java
Java 实现汉字按照首字母分组排序
Java 实现汉字按照首字母分组排序
715 0
|
XML 网络协议 Dubbo
【Java面试】RPC(Remote Procedure Call)
【Java面试】RPC(Remote Procedure Call)
189 0
|
Java 数据安全/隐私保护
JAVA 实现上传图片添加水印(详细版)(上)
JAVA 实现上传图片添加水印(详细版)
1265 0
JAVA 实现上传图片添加水印(详细版)(上)
|
XML 存储 JSON
JAVA面试——Netty 与 RPC(二)
JAVA面试——Netty 与 RPC
136 0
JAVA面试——Netty 与 RPC(二)
|
XML 编解码 弹性计算
JAVA面试——Netty 与 RPC(一)
JAVA面试——Netty 与 RPC
217 0
JAVA面试——Netty 与 RPC(一)