RPC
解决的问题
RPC 次要是为了解决的两个问题:
- 解决分布式系统中,服务之间的调用问题。
- 近程调用时,要可能像本地调用一样不便,让调用者感知不到近程调用的逻辑。
这一节咱们来学习下如何基于 websocket 实现最简略的 rpc 调用,后续会实现基于 netty4 的版本。
开源地址: https://github.com/houbb/rpc
残缺流程
其中右边的Client,对应的就是后面的Service A,而左边的Server,对应的则是Service B。
上面一步一步具体解释一下。
- Service A的应用层代码中,调用了Calculator的一个实现类的add办法,心愿执行一个加法运算;
- 这个Calculator实现类,外部并不是间接实现计算器的加减乘除逻辑,而是通过近程调用Service B的RPC接口,来获取运算后果,因而称之为Stub;
- Stub怎么和Service B建设近程通信呢?这时候就要用到近程通信工具了,也就是图中的Run-time Library,这个工具将帮你实现近程通信的性能,比方Java的Socket,就是这样一个库,当然,你也能够用基于Http协定的HttpClient,或者其余通信工具类,都能够,RPC并没有规定说你要用何种协定进行通信;
- Stub通过调用通信工具提供的办法,和Service B建设起了通信,而后将申请数据发给Service B。须要留神的是,因为底层的网络通讯是基于二进制格局的,因而这里Stub传给通信工具类的数据也必须是二进制,比方calculator.add(1,2),你必须把参数值1和2放到一个Request对象外头(这个Request对象当然不只这些信息,还包含要调用哪个服务的哪个RPC接口等其余信息),而后序列化为二进制,再传给通信工具类,这一点也将在上面的代码实现中体现;
- 二进制的数据传到Service B这一边了,Service B当然也有本人的通信工具,通过这个通信工具接管二进制的申请;
- 既然数据是二进制的,那么天然要进行反序列化了,将二进制的数据反序列化为申请对象,而后将这个申请对象交给Service B的Stub解决;
- 和之前的Service A的Stub一样,这里的Stub也同样是个“假玩意”,它所负责的,只是去解析申请对象,晓得调用方要调的是哪个RPC接口,传进来的参数又是什么,而后再把这些参数传给对应的RPC接口,也就是Calculator的理论实现类去执行。很显著,如果是Java,那这里必定用到了反射。
- RPC接口执行结束,返回执行后果,当初轮到Service B要把数据发给Service A了,怎么发?一样的情理,一样的流程,只是当初Service B变成了Client,Service A变成了Server而已:Service B反序列化执行后果->传输给Service A->Service A反序列化执行后果 -> 将后果返回给Application,结束。
简略实现
假如服务 A,想调用服务 B 的一个办法。
因为不在同一个内存中,无奈间接应用。如何能够实现相似 Dubbo 的性能呢?
这里不须要应用 HTTP 级别的通信,应用 TCP 协定即可。
common
专用模块,定义通用对象。
- Rpc 常量
public interface RpcConstant { /** * 地址 */ String ADDRESS = "127.0.0.1"; /** * 端口号 */ int PORT = 12345;}
- 申请入参
public class RpcCalculateRequest implements Serializable { private static final long serialVersionUID = 6420751004355300996L; /** * 参数一 */ private int one; /** * 参数二 */ private int two; //getter & setter & toString()}
- 服务接口
public interface Calculator { /** * 计算加法 * @param one 参数一 * @param two 参数二 * @return 返回后果 */ int add(int one, int two);}
server
- 服务接口的实现
public class CalculatorImpl implements Calculator { @Override public int add(int one, int two) { return one + two; }}
- 启动服务
public static void main(String[] args) throws IOException { Calculator calculator = new CalculatorImpl(); try (ServerSocket listener = new ServerSocket(RpcConstant.PORT)) { System.out.println("Server 端启动:" + RpcConstant.ADDRESS + ":" + RpcConstant.PORT); while (true) { try (Socket socket = listener.accept()) { // 将申请反序列化 ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); Object object = objectInputStream.readObject(); System.out.println("Request is: " + object); // 调用服务 int result = 0; if (object instanceof RpcCalculateRequest) { RpcCalculateRequest calculateRpcRequest = (RpcCalculateRequest) object; result = calculator.add(calculateRpcRequest.getOne(), calculateRpcRequest.getTwo()); } // 返回后果 ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); objectOutputStream.writeObject(result); } catch (Exception e) { e.printStackTrace(); } } }}
启动日志:
Server 端启动:127.0.0.1:12345
client
- 客户端调用
public static void main(String[] args) { Calculator calculator = new CalculatorProxy(); int result = calculator.add(1, 2); System.out.println(result);}
- 计算的代理类
public class CalculatorProxy implements Calculator { @Override public int add(int one, int two) { try { Socket socket = new Socket(RpcConstant.ADDRESS, RpcConstant.PORT); // 将申请序列化 RpcCalculateRequest calculateRpcRequest = new RpcCalculateRequest(one, two); ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); // 将申请发给服务提供方 objectOutputStream.writeObject(calculateRpcRequest); // 将响应体反序列化 ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); Object response = objectInputStream.readObject(); if (response instanceof Integer) { return (Integer) response; } else { throw new RuntimeException(); } } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } }}
- 调用日志
client 端
3
server 端
Server 端启动:127.0.0.1:12345Request is: RpcCalculateRequest{one=1, two=2}
开源地址
为了便于大家学习,以上源码曾经开源:
https://github.com/houbb/rpc
我是老马,期待与你的下次重逢。