最简单的RPC框架实现

简介: 最简单的RPC框架实现

 RPC架构

RPC的全称是 Remote Procedure Call,它是一种进程间通信方式。允许像调用本地服务一样调用远程服务,它的具体实现方式可以不同,例如Spring 的 HTTP Invoker, Facebook的Thrift二进制私有协议通信。

RPC概念术语在上世纪80年代由Bruce Jay Nelson提出,在他的论文中对RPC进行了如下总结。

(1)简单:RPC概念的语义十分清晰和简单,这样建立分布式计算就更容易。

(2)高效:过程调用看起来十分简单而且高效。

(3)通用:在单机计算中过程往往是不同算法和API,跨进程调用最重要的是通用的通信机制。

2006年之后,随着移动互联网的发展,各种智能终端的普及,远程分布式调用已经成为主流,RPC框架也如雨后春笋般诞生,开源和自研的RPC框架的普及标志着传统垂直应用架构时代的终结。

RPC框架原理

RPC框架的目标就是让远程过程(服务)调用更加简单、透明,RPC框架负责屏蔽底层的传输方式(TCP或者UDP)、序列化方式(XMLIJSON/二进制)和通信细节。框架使用者只需要了解谁在什么位置提供了什么样的远程服务接口即可,开发者不需要关心底层通信细节和调用过程。

image.gif编辑

RPC框架实现的几个核心技术点如下:

(1)远程服务提供者需要以某种形式提供服务调用相关的信息,包括但不限于服务接口定义、数据结构,或者中间态的服务定义文件,例如Thrift的IDL文件,WS-RPC的WSDL文件定义,甚至也可以是服务端的接口说明文档;服务调用者需要通过一定的途径获取远程服务调用相关信息,例如服务端接口定义Jar包导入,获取服务端IDL文件等。

(2)远程代理对象:服务调用者调用的服务实际是远程服务的本地代理,对于Java语言,它的实现就是JDK的动态代理,通过动态代理的拦截机制,将本地调用封装成远程服务调用。

(3)通信:RPC框架与具体的协议无关,例如Spring的远程调用支持HTTP Invoke、RMl Invoke,MessagePack使用的是私有的二进制压缩协议。

(4)序列化:远程通信,需要将对象转换成二进制码流进行网络传输,不同的序列化框架,支持的数据类型、数据包大小、异常类型及性能等都不同。不同的RPC框架应用场景不同,因此技术选择也会存在很大差异。一些做得比较好的RPC框架,可以支持多种序列化方式,有的甚至支持用户自定义序列化框架(Hadoop Avro) 。

简单的RPC框架实现

下面通过Java原生的序列化、Socket通信、动态代理和反射机制,实现最简单的RPC框架。

它由三部分组成:

(1)服务提供者,它运行在服务端,负责提供服务接口定义和服务实现类。

(2)服务发布者,它运行在RPC服务端,负责将本地服务发布成远程服务,供其他消费者调用。

(3)本地服务代理,它运行在RPC客户端,通过代理调用远程服务提供者,然后将结果进行封装返回给本地消费者。

下面看具体代码实现,代码如下:

EchoService接口代码

/**
 * 服务端接口
 */
public interface EchoService {
    String echo(String ping);
}

image.gif

EchoService接口的实现类代码

public class EchoServiceImpl implements EchoService {
    public String echo(String ping) {
        return ping != null ? ping +" -->  I am OK." : "I am OK.";
    }
}

image.gif

RPC服务端服务发布者代码

/**
 * 服务端发布者
 */
public class RpcExporter {
    static Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    public static void exporter(String hostName,int port) throws Exception{
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(hostName,port));
        try{
            while (true){
                executor.execute(new ExporterTask(serverSocket.accept()));
            }
        }finally {
            serverSocket.close();
        }
    }
    private static class ExporterTask implements Runnable{
        Socket client = null;
        public ExporterTask(Socket client){
            this.client = client;
        }
        public void run() {
            ObjectInputStream inputStream = null;
            ObjectOutputStream outputStream = null;
            try{
                inputStream = new ObjectInputStream(client.getInputStream());
                String interfaceName = inputStream.readUTF();
                Class<?> service = Class.forName(interfaceName);
                String methodName = inputStream.readUTF();
                Class<?>[] parameterTypes = (Class<?>[]) inputStream.readObject();
                Object[] arguments = (Object[]) inputStream.readObject();
                Method method = service.getMethod(methodName, parameterTypes);
                Object result = method.invoke(service.newInstance(), arguments);
                outputStream = new ObjectOutputStream(client.getOutputStream());
                outputStream.writeObject(result);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
                if (inputStream != null) {
                    try{
                        inputStream.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
                if (client != null) {
                    try{
                        client.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

image.gif

服务发布者的主要职责如下:

(1)作为服务端,监听客户端的TCP连接,接收到新的客户端连接之后,将其封装成Task,由线程池执行。

(2)将客户端发送的码流反序列化成对象,反射调用服务实现者,获取执行结果。

(3)将执行结果对象反序列化,通过Socket发送给客户端。

(4)远程服务调用完成之后,释放Socket等连接资源,防止句柄泄漏。

RPC客户端本地服务代理:

/**
 * 客户端本地服务
 * @param <S>
 */
public class RpcImporter<S> {
    public S importer(final Class<?>serviceClass, final InetSocketAddress addr){
        return (S)Proxy.newProxyInstance(serviceClass.getClassLoader(),
                new Class<?>[] {
                    serviceClass.getInterfaces()[0]
                },
                new InvocationHandler(){
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Socket socket = null;
                        ObjectInputStream inputStream = null;
                        ObjectOutputStream outputStream = null;
                        try{
                            socket = new Socket();
                            socket.connect(addr);
                            outputStream = new ObjectOutputStream(socket.getOutputStream());
                            outputStream.writeUTF(serviceClass.getName());
                            outputStream.writeUTF(method.getName());
                            outputStream.writeObject(method.getParameterTypes());
                            outputStream.writeObject(args);
                            inputStream = new ObjectInputStream(socket.getInputStream());
                            return inputStream.readObject();
                        }finally {
                            if (socket != null) {
                                socket.close();
                            }
                            if (outputStream != null) {
                                outputStream.close();
                            }
                            if (inputStream != null) {
                                inputStream.close();
                            }
                        }
                    }
                }
        );
    }
}

image.gif

本地服务代理的主要功能如下:

(1)将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用。

(2)创建Socket客户端,根据指定地址连接远程服务提供者。

(3)将远程服务调用所需的接口类、方法名、参数列表等编码后发送给服务提供者。

(4)同步阻塞等待服务端返回应答,获取应答之后返回。

测试代码如下:

public class RpcTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    RpcExporter.exporter("localhost",8888);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        RpcImporter<EchoService> importer = new RpcImporter<EchoService>();
        EchoService echo = importer.importer(EchoServiceImpl.class, new InetSocketAddress("localhost", 8888));
        System.out.println(echo.echo(" Hello! I am BanQ! "));
    }
}

image.gif

首先,创建一个异步发布服务端的线程并启动,用于接收RPC客户端的请求,根据请求参数调用服务实现类,返回结果给客户端。随后,创建客户端服务代理类,构造 RPC请求参数,发起RPC调用,将调用结果输出到控制台上。

执行结果如下:

Hello! I am BanQ!  -->  Hi! BanQ

image.gif

最后附上源码: 最简单的RPC框架源码

目录
相关文章
|
6月前
|
负载均衡 Dubbo Java
Dubbo 3.x:探索阿里巴巴的开源RPC框架新技术
随着微服务架构的兴起,远程过程调用(RPC)框架成为了关键组件。Dubbo,作为阿里巴巴的开源RPC框架,已经演进到了3.x版本,带来了许多新特性和技术改进。本文将探讨Dubbo 3.x中的一些最新技术,包括服务注册与发现、负载均衡、服务治理等,并通过代码示例展示其使用方式。
359 9
|
6月前
|
JSON 负载均衡 网络协议
Rpc编程系列文章第二篇:RPC框架设计目标
Rpc编程系列文章第二篇:RPC框架设计目标
|
6月前
|
设计模式 负载均衡 网络协议
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
263 0
|
16天前
|
自然语言处理 负载均衡 API
gRPC 一种现代、开源、高性能的远程过程调用 (RPC) 可以在任何地方运行的框架
gRPC 是一种现代开源高性能远程过程调用(RPC)框架,支持多种编程语言,可在任何环境中运行。它通过高效的连接方式,支持负载平衡、跟踪、健康检查和身份验证,适用于微服务架构、移动设备和浏览器客户端连接后端服务等场景。gRPC 使用 Protocol Buffers 作为接口定义语言,支持四种服务方法:一元 RPC、服务器流式处理、客户端流式处理和双向流式处理。
|
6月前
|
Dubbo Java 应用服务中间件
Rpc编程系列文章第三篇:Hessian RPC一个老的RPC框架
Rpc编程系列文章第三篇:Hessian RPC一个老的RPC框架
|
3月前
|
Dubbo 网络协议 Java
RPC框架:一文带你搞懂RPC
这篇文章全面介绍了RPC(远程过程调用)的概念、原理和应用场景,解释了RPC如何工作以及为什么在分布式系统中广泛使用,并探讨了几种常用的RPC框架如Thrift、gRPC、Dubbo和Spring Cloud,同时详细阐述了RPC调用流程和实现透明化远程服务调用的关键技术,包括动态代理和消息的编码解码过程。
RPC框架:一文带你搞懂RPC
|
2月前
|
XML 负载均衡 监控
分布式-dubbo-简易版的RPC框架
分布式-dubbo-简易版的RPC框架
|
3月前
|
XML 存储 JSON
(十二)探索高性能通信与RPC框架基石:Json、ProtoBuf、Hessian序列化详解
如今这个分布式风靡的时代,网络通信技术,是每位技术人员必须掌握的技能,因为无论是哪种分布式技术,都离不开心跳、选举、节点感知、数据同步……等机制,而究其根本,这些技术的本质都是网络间的数据交互。正因如此,想要构建一个高性能的分布式组件/系统,不得不思考一个问题:怎么才能让数据传输的速度更快?
|
5月前
|
存储 缓存 Linux
【实战指南】嵌入式RPC框架设计实践:六大核心类构建高效RPC框架
在先前的文章基础上,本文讨论如何通过分层封装提升一个针对嵌入式Linux的RPC框架的易用性。设计包括自动服务注册、高性能通信、泛型序列化和简洁API。框架分为6个关键类:BindingHub、SharedRingBuffer、Parcel、Binder、IBinder和BindInterface。BindingHub负责服务注册,SharedRingBuffer实现高效数据传输,Parcel处理序列化,而Binder和IBinder分别用于服务端和客户端交互。BindInterface提供简单的初始化接口,简化应用集成。测试案例展示了客户端和服务端的交互,验证了RPC功能的有效性。
401 7
|
4月前
|
分布式计算 负载均衡 数据安全/隐私保护
什么是RPC?有哪些RPC框架?
RPC(Remote Procedure Call,远程过程调用)是一种允许运行在一台计算机上的程序调用另一台计算机上子程序的技术。这种技术屏蔽了底层的网络通信细节,使得程序间的远程通信如同本地调用一样简单。RPC机制使得开发者能够构建分布式计算系统,其中不同的组件可以分布在不同的计算机上,但它们之间可以像在同一台机器上一样相互调用。
151 8