系统间通信:基于TCP协议的RPC实现范例
一、RPC名词解释
RPC的全称是Remote Process Call,即远程过程调用,它应用广泛,实现方式也很多,拥有RMI、WebService等诸多成熟的方案,在业界得到了广泛的使用。单台服务的处理能力受到硬件成本的限制,不可能无限制地提升。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能,这是系统发展到一定阶段必然性的变革,也是实现分布式计算的基础。
RPC的实现包括客户端和服务端,即服务的调用方与服务的提供方。服务调用方发送RPC请求到服务提供方,服务提供方根据调用方提供的参数执行请求方法,将执行结果返回给调用方,一次RPC调用完成。这里涉及到网络通信、序列化与反序列化的操作,如果服务提供者是多台或者服务提供者进行了分组,还会涉及到负载均衡与路由。
二、RPC调用过程
随着业务的发展,服务调用者的规则发展到一定阶段,对服务提供方的压力也日益增加,因此,服务需要进行扩容。而随着服务提供者的增加与业务的发展,不同的服务之间还需要进行分组,以隔离不同的业务,避免相互影响,在这种情况下,服务的路由和负载均衡成为必须考虑的问题。服务消费者通过获取服务提供者的分组信息和地址信息进行路由,如果服务提供者为一个集群而非单台机器,则需要根据相应的负载均衡策略,选取其中一台进行调研,有关服务的路由和负载均衡,后续章节会进行详细介绍RPC调用过程(不包括负载均衡和路由)如下图所示:
(1) 服务消费方(client)调用以本地调用方式调用服务;;
(2) clientstub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体,并将消息发送到服务端(对象序列化);
(3) serverstub收到消息后进行解码(对象反序列化);
(4) serverstub根据解码结果调用本地的服务;
(5) 本地服务执行并将结果返回给server stub;
(6) serverstub将返回结果打包成消息并发送至消费方;
(7) clientstub接收到消息,并进行解码;
(8) 服务消费方得到最终结果。
RPC的目标就是要2~7这些步骤都封装起来,让用户对这些细节透明。(注意:无论是何种类型的数据,最终都需要转换二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。目前成熟的方案有多种,比如Protocol Buffers、Java自带方式、Hessian、JSON、XML等)。各种序列化方式性能对比图如下所示:
三、范例代码
基于Java的Socket API,我实现一个简单的RPC调用的范例,在这个范例中,包括服务的接口、接口的远程实现、服务的提供方以及服务的消费方。为了方便大家下载代码运行,我把代码放到了Github(https://github.com/dreamerkr/RPCDemo),其类图如下所示:
(1) 服务接口SayHelloservice
package com.primeton.RPCDemo;
public interface SayHelloService {
/**
* 问候接口
* @param msg
* @return
*/
public String sayHello(String msg);
}
(2) 接口远端实现
package com.primeton.RPCDemo;
public class SayHelloServiceImpl implements SayHelloService {
@Override
public String sayHello(String msg) {
if("hello".equals(msg)){
return "hello client";
}else{
return "bye bye";
}
}
}
(3) 服务消费者
package com.primeton.RPCDemo;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.Socket;
/**
* TODO 服务消费者
*
* @author wangzhao (mailto:[email protected])
*/
public class Consumer {
public static void main(String[] args) throws Exception {
//构造需要调用的方法
String interfaceName = SayHelloService.class.getName();
Method method = SayHelloService.class.getMethod("sayHello",
java.lang.String.class);
Object[] argments = {"hello"};
//发送调用信息到服务器端,调用相应的服务
Socket socket = new Socket("127.0.0.1",1234);
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeUTF(interfaceName);
outputStream.writeUTF(method.getName());
outputStream.writeObject(method.getParameterTypes());
outputStream.writeObject(argments);
System.out.println("发送信息到服务端,发送的信息为:"+argments[0]);
//服务返回的结果
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
Object object = inputStream.readObject();
System.out.println("服务返回的结果为"+object);
}
}
(4) 服务提供者
package com.primeton.RPCDemo;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* RPC服务提供者
*
* @author wangzhao (mailto:[email protected])
*/
public class Provider {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(1234);
Map
服务提供者端事先将服务实例化好后放在services这个Map中(此处涉及服务的路由被简化处理了,后面章节会详细讲解),通过一个while循环,不断地接收到来的请求,得到所需的参数,包括接口名称、方法名称、参数类型和参数,通过Java的反射取得接口所需调用的方法,执行后将结果返回给服务的消费者。
在真实的生产环境中,常常是多个客户端同时发送多个请求到服务端,服务端则需要同时接收和处理多个客户端请求,涉及并发处理、路由、负载均衡、监控、日志记录等功能,这个是无法满足的。下一章我将给大家分享基于HTTP协议的RPC实现的范例。