本文以极简的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的 序列化层,废话少说直接上代码:
更多内容请关注【程猿薇茑】