为什么需要序列化?
序列化的分类与效率
为什么要有RPC?
RPC是怎么实现的?
前言
一年以来,工作中也用过很多关于分布式的framework,但从来没有系统的归纳总结过。打算开一个分支来记录这一年以来对分布式的积累与理解。碍于工作年限较短,无法从整体构架来分析分布式系统,因此打算从底层的序列化和rpc开启篇章。
为什么需要序列化?
In computer science, in the context of data storage, serialization (or serialisation) is the process of translating data structures or object state into a format that can be stored (for example, in a file or memory buffer) or transmitted (for example, across a networkconnection link) and reconstructed later (possibly in a different computer environment).
序列化是将一个结构或对象转换成一种可以被存储或传输的状态,且这种状态可以被反向的重建这个结构或对象。
可以看到,序列化其实是一种事物表达形式的转换,它可以把一个人类世界的复杂对象,转换为计算机世界可识别的数据表现形式,并且可以逆向此过程。因此我对序列化的理解就是:序列化是复杂数据类型与二进制数据的媒介。
无论是磁盘存储还是网络传输,我们的信息最终会被序列化为二进制流。
序列化的分类与效率
目前常用的序列化格式有JSON、XML、Thrift、Protocol Buffer等。但从宏观来看,主要分为用户可读和二进制类两种。这两种方式各有利弊,JSON这类可读型更用户友好且更容易映射至一般语言的数据结构,适合web类研发与交互。PB这种二进制编码格式的序列化方式有着更紧凑的编码方式和更高效的编解码效率,适合大请求量的场景。因此针对业务选择更合适的序列化协议。
PB协议编码与原理 很好的解释了问什么PB的效率如此的高,以及一些更好的应用方式。总体来讲,PB就是通过数据的位移和简单运算实现紧凑编码,此外合理的编码直接去除了分隔符,进一步减小了编码后的数据量,使得其非常适合大请求数量的场景。
说完了序列化,就向上走一步说一说由序列化为底层实现的Remote Procedure Call.
为什么要有RPC?
随着业务的发展,服务越来越多,不可避免的会将这些服务分布在不同的服务器甚至不同机房的服务器上,那么这时就需要一种远程交互手段来实现服务的请求与处理。
In distributed computing, a remote procedure call (RPC) is when a computer program causes a procedure (subroutine) to execute in a different address space (commonly on another computer on a shared network), which is coded as if it were a normal (local) procedure call, without the programmer explicitly coding the details for the remote interaction
RPC是能像本地调用函数一样地调用一个处于不同地址空间的函数过程,且不需要去关心与远程调用相关的额外代码。
从操作系统的角度来讲,一个进程(线程)在调用一个本地函数时,会将当前方法的状态压栈存入内存,然后开启一个新的栈帧来描述这个本地函数的状态。但如果是不同进程之间通信,那就需要用到IPC通信,使得不同地址空间的进程交换数据。如果更复杂,让不同计算机上的不同进程进行调用通信,那还要涉及到网络连接与处理。而RPC就是希望可以透明化这些IPC及网络调用,使得程序员只关心该有的业务逻辑,简化开发的一种方式。此外部分RPC是自定义底层通信协议,会比HTTP这类通用协议更高效、更适合特定业务。
RPC是怎么实现的?
RPC调用过程
1)服务消费方(client)调用以本地调用方式调用服务;
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
3)client stub找到服务地址,并将消息发送到服务端;
4)server stub收到消息后进行解码;
5)server stub根据解码结果调用本地的服务;
6)本地服务执行并将结果返回给server stub;
7)server stub将返回结果打包成消息并发送至消费方;
8)client stub接收到消息,并进行解码;
9)服务消费方得到最终结果
public class RPCProxyClient implements java.lang.reflect.InvocationHandler{
private Object obj;
public RPCProxyClient(Object obj){
this.obj=obj;
}
/**
* 得到被代理对象;
*/
public static Object getProxy(Object obj){
return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new RPCProxyClient(obj));
}
/**
* 调用此方法执行
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//结果参数;
Object result = new Object();
// ...执行通信相关逻辑
// ...
return result;
}
}
public class Test {
public static void main(String[] args) {
HelloWorldService helloWorldService = (HelloWorldService)RPCProxyClient.getProxy(HelloWorldService.class);
helloWorldService.sayHello("test");
}
}
在Java里,实现调用过程透明化的是动态代理,本地方法调用代理,代理完成编码、网络调用等额外作业,这些作业对上层应用透明。以此达到RPC调用的。Java原生 RPC的示例 用非常清晰示例说明了一个RPC的基本Java实现。至此,关于序列化和RPC的最核心内容已经写完了,关于注册中心、监控等服务化治理内容,将在后续文章中继续。