RPC简介
RPC是远程过程调用(Remote Procedure Call)的缩写形式。
客户端方法:服务调用方所调用的接口
客户端代理:将接口封装成代理对象,并序列化请求参数、反序列化响应结果,使用远程传输协议调用服务端,(例如:Socket、Netty、RMI、HTTP等)。
远程网络调用:采用远程传输协议进行通信实现数据的传输。
服务端代理:服务端收到远程请求后,将二进制的数据反序列化为请求对象,然后调用本地接口返回响应数据并序列化结果发送出去。
服务端方法:服务提供者具体的实现,也就是最终请求的方法。
RPC的简单实现
接下来通过一个简单的demo来分析RPC调用过程
源码git地址:https://github.com/xxiangzh/x-rpc
项目结构,consumer为服务调用者,即客户端;provider为服务提供者,即服务端。
接口调用过程
代码详情
用户接口(客户端和服务端相同,复制过去即可)
public interface UserService {
String getUserNameById(Long userId);
}
RPC通信参数(客户端和服务端相同,复制过去即可)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RpcRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String methodName;
private Long parameter;
}
用户接口实现类(客户端通过远程RPC最终调用的是这个接口)
public class UserServiceImpl implements UserService {
public String getUserNameById(Long userId) {
return userId > 0 ? "向振华" : "无名";
}
}
服务提供者RPC通信类,采用socket通信
public class RpcHandler {
public void run() throws Exception {
ServerSocket serverSocket = new ServerSocket(8081);
while (true) {
Socket socket = serverSocket.accept();
// 将请求体反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object object = objectInputStream.readObject();
// 服务调用
Object result = null;
if (object instanceof RpcRequest) {
RpcRequest rpcRequest = (RpcRequest) object;
if ("getUserNameById".equals(rpcRequest.getMethodName())) {
UserService userService = new UserServiceImpl();
result = userService.getUserNameById(rpcRequest.getParameter());
} else {
throw new RuntimeException("method not found");
}
}
// 结果返回
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(result);
}
// TODO socket.close(); serverSocket.close();
}
}
然后启动服务提供者
public class ProviderTest {
public static void main(String[] args) throws Exception {
new RpcHandler().run();
}
}
服务调用者,用户接口的实现,这里是通过RPC方式调用
public class RpcUserServiceImpl implements UserService {
public String getUserNameById(Long userId) {
try {
Socket socket = new Socket("127.0.0.1", 8081);
// 将请求体序列化并发给服务提供方
RpcRequest rpcRequest = new RpcRequest("getUserNameById", userId);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(rpcRequest);
// 将响应体反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object response = objectInputStream.readObject();
// 返回结果
return (String) response;
} catch (Exception e) {
return null;
}
// TODO socket.close();
}
}
服务调用者测试类
public class ConsumerTest {
public static void main(String[] args) {
UserService userService = new RpcUserServiceImpl();
System.out.println(userService.getUserNameById(1L));
System.out.println(userService.getUserNameById(-1L));
}
}
启动服务端,然后再运行客户端测试类,即可得到结果。通过这个demo可以了解到RPC远程调用的大致流程,但是真正的RPC框架远不止这么简单。
真正的RPC会对接口代理、对请求信息统一封装通过反射实例化,通过注册中心维护服务器实例,通过负载均衡提供服务的灵活性和可用性,另外还有监控、拦截器、日志等等。
Dubbo
版本:2.7
Dubbo(读音[ˈdʌbəʊ])是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
接下来看看Dubbo服务调用过程
图片来源于:https://dubbo.apache.org/zh/docs/v2.7/dev/design/
各层说明
- config 配置层:对外配置接口,以
ServiceConfig
,ReferenceConfig
为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类 - proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以
ServiceProxy
为中心,扩展接口为ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为
RegistryFactory
,Registry
,RegistryService
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以
Invoker
为中心,扩展接口为Cluster
,Directory
,Router
,LoadBalance
- monitor 监控层:RPC 调用次数和调用时间监控,以
Statistics
为中心,扩展接口为MonitorFactory
,Monitor
,MonitorService
- protocol 远程调用层:封装 RPC 调用,以
Invocation
,Result
为中心,扩展接口为Protocol
,Invoker
,Exporter
- exchange 信息交换层:封装请求响应模式,同步转异步,以
Request
,Response
为中心,扩展接口为Exchanger
,ExchangeChannel
,ExchangeClient
,ExchangeServer
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以
Message
为中心,扩展接口为Channel
,Transporter
,Client
,Server
,Codec
- serialize 数据序列化层:可复用的一些工具,扩展接口为
Serialization
,ObjectInput
,ObjectOutput
,ThreadPool
整个调用链如下图所示
整个过程看起来负责多了,但是核心流程并没变,只是加了很多高可用、高性能的扩展。
客户端通过ProxyFactory代理工厂创建代理类,然后进入过滤器链路,然后构建Invoker对象,通过注册中心发现服务,通过负载均衡策略选择远程服务提供者,发起远程调用,然后通过远程传输协议将数据发送到服务端,服务端接收到请求后对数据进行解析、序列化,从线程池获取服务线程执行,再进入服务提供者的过滤器链路,然后端通过ProxyFactory代理工厂创建代理类构建Invoker对象,执行本地方并返回结果。
Spring Cloud Feign
Feign是一个声明式的REST客户端,它的目的就是让REST调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
图来源于:https://www.cnblogs.com/crazymakercircle/p/11965726.html
Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。
Feign默认采用HttpURLConnection进行远程通信,新能比较差,可以改为OKHttp或者Netty等。