rpc-01-简介

rpc-01-简介

前言

  • rpc并不是一种协议,而是一种开发的理念
  • 底层本质都是传输二进制流(TCP可以进行传输)

概念阐述

  • RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
  • 比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。

最终解决的问题:让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。

0. RPC 原理

0.1 例子

  • RPC就是要像调用本地的函数一样去调远程函数。
    • 在研究RPC前,我们先看看本地调用是怎么调的。假设我们要调用函数Multiply来计算lvalue * rvalue的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1 int Multiply(int l, int r) {
2 int y = l * r;
3 return y;
4 }
5
6 int lvalue = 10;
7 int rvalue = 20;
8 int l_times_r = Multiply(lvalue, rvalue);

那么在第8行时,我们实际上执行了以下操作:
将 lvalue 和 rvalue 的值压栈进入Multiply函数,取出栈中的值1020,将其赋予 l 和 r执行第2行代码,
计算 l * r ,并将结果存在 y将 y 的值压栈,
然后从Multiply返回第8行,从栈中取出返回值 200 ,并赋值给 l_times_r以上5步就是执行本地调用的过程。
(注:以上步骤只是为了说明原理。事实上编译器经常会做优化,对于参数和返回值少的情况会直接将其存放在寄存器,而不需要压栈弹栈的过程,甚至都不需要调用call,而直接做inline操作。仅就原理来说,这5步是没有问题的。)
  • 远程调用带来的问题:
    • 在远程调用时,我们需要执行的函数体是在远程的机器上的,也就是说,Multiply是在另一个进程中执行的。这就带来了几个新问题:
  1. Call ID映射

    • 我们怎么告诉远程机器我们要调用Multiply,而不是Add或者FooBar呢?
    • 在本地调用中,函数体是直接通过函数指针来指定的,我们调用Multiply,编译器就自动帮我们调用它相应的函数指针。
    • 但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。
    • 所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。
    • 客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 <–> Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。
    • 当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
  2. 序列化和反序列化。客户端怎么把参数值传给远程的函数呢?

    • 在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。
    • 但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。
    • 这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
    • 常见的有:protobuf,hessian,JSON
  3. 网络传输 :远程调用往往用在网络上,客户端和服务端是通过网络连接的。

    • 所有的数据都需要通过网络传输,因此就需要有一个网络传输层。
    • 网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。
    • 只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。
    • 尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty也属于这层的东西。

有了这三个机制,就能实现RPC了,具体过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Client端 
// int l_times_r = Call(ServerAddr, Multiply, lvalue, rvalue)
1. 将这个调用映射为Call ID。这里假设用最简单的字符串当Call ID的方法
2. 将Call ID,lvalue和rvalue序列化。可以直接将它们的值以二进制形式打包
3.2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给l_times_r

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用std::map<std::string, std::function<>>
2. 等待请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在call_id_map中查找,得到相应的函数指针
5. 将lvalue和rvalue反序列化后,在本地调用Multiply函数,得到结果
6. 将结果序列化后通过网络返回给Client

所以要实现一个RPC框架,其实只需要按以上流程实现就基本完成了。

其中:

  • Call ID映射可以直接使用函数字符串,也可以使用整数ID。映射表一般就是一个哈希表。

  • 序列化反序列化可以自己写,也可以使用Protobuf或者JSON之类的。

  • 网络传输库可以自己写socket,或者用asio,ZeroMQ,Netty之类。

  • 当然,这里面还有一些细节可以填充,比如如何处理网络错误,如何防止攻击,如何做流量控制,等等。但有了以上的架构,这些都可以持续加进去。

接下来将以栗子来说明RPC的演进迭代过程

0.2 流程小结

  1. 服务消费方(client)调用以本地调用方式调用服务;
  2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
  3. client stub找到服务地址,并将消息发送到服务端;
  4. server stub收到消息后进行解码;
  5. server stub根据解码结果调用本地的服务;
  6. 本地服务执行并将结果返回给server stub;
  7. server stub将返回结果打包成消息并发送至消费方;
  8. client stub接收到消息,并进行解码;
  9. 服务消费方得到最终结果。

0.3 业界RPC

  • 业界常用的RPC框架

Dubbo: Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。目前 Dubbo 已经成为 Spring Cloud Alibaba 中的官方组件。

gRPC :gRPC 是可以在任何环境中运行的现代开源高性能RPC框架。它可以通过可插拔的支持来有效地连接数据中心内和跨数据中心的服务,以实现负载平衡,跟踪,运行状况检查和身份验证。它也适用于分布式计算的最后一英里,以将设备,移动应用程序和浏览器连接到后端服务。

0.4 RPC / HTTP

  • 首先需要指正,这两个并不是并行概念。RPC 是一种设计,就是为了解决不同服务之间的调用问题,完整的 RPC 实现一般会包含有 传输协议序列化协议 这两个。

  • 而 HTTP 是一种传输协议,RPC 框架完全可以使用 HTTP 作为传输协议,也可以直接使用 TCP,使用不同的协议一般也是为了适应不同的场景。

0.5 序列化

  • 通过将对象序列化成字节数组,即可将对象发送到网络中。
  • 序列化serialization)在计算机科学的资料处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。
  • 依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。
  • 这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。

在Java中的操作:

  • 在 Java 中,想要序列化一个对象,这个对象所属的类必须实现了 Serializable 接口,并且其内部属性必须都是可序列化的。如果有一个属性不是可序列化的,则该属性必须被声明为 transient
  • JDK 中提供了 ObjectOutStream 类来对对象进行序列化。

常见的序列化方式: JSON、Kryo、Hessian 和 Protobuf 的序列化。

  • JSON 是一种轻量级的数据交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象,类似 xml,Jsonxml更小、更快更容易解析。JSON 由于采用字符方式存储,占用相对于字节方式较大,并且序列化后类的信息会丢失,可能导致反序列化失败。
  • 剩下的都是基于字节的序列化
    • Kryo是一个快速高效的 Java 序列化框架,旨在提供快速、高效和易用的 API。无论文件、数据库或网络数据 Kryo 都可以随时完成序列化。 Kryo 还可以执行自动深拷贝、浅拷贝。这是对象到对象的直接拷贝,而不是对象->字节->对象的拷贝。kryo 速度较快,序列化后体积较小,但是跨语言支持较复杂。
    • protobuf(Protocol Buffers)是由 Google 发布的数据交换格式,提供跨语言、跨平台的序列化和反序列化实现,底层由 C++ 实现,其他平台使用时必须使用 protocol compiler 进行预编译生成 protoc 二进制文件。性能主要消耗在文件的预编译上。序列化反序列化性能较高,平台无关。
    • Hessian 是一个基于二进制的协议,Hessian 支持很多种语言,例如 Java、python、c++,、net/c#、D、Erlang、PHP、Ruby、object-c等,它的序列化和反序列化也是非常高效。速度较慢,序列化后的体积较大。

1. 代码举例

1.1 环境搭建

  1. User.java : 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class User implements Serializable {
private static final long serialVersionUID = 1L;
int id;
String name;

public User(int id, String name) {
this.id = id;
this.name = name;
}

public int getId() {
return id;
}

public void setId(int 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 + '\'' +
'}';
}
}
  1. IUserService : 寻找用户id
1
2
3
4
5
6
7
8
9
10
public interface IUserService {
User findUserById(int id);
}

public class IUserServiceImpl implements IUserService {
@Override
public User findUserById(int id) {
return new User(id,"Alice");
}//直接new模拟数据库查询
}

1.1 rpc-01-原始方式

  • 使用二进制流
  1. Server 端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Server {
private static boolean running = true;
public static void main(String[] args) throws Exception {
// 1. 在端口进行监听
ServerSocket server = new ServerSocket(8088);
while(running){
Socket client = server.accept();
process(client); // 处理传过来的请求
client.close();
}
server.close();
}

// 2. 处理请求
public static void process(Socket socket) throws Exception {
// 2.1 二进制流对象
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());

// 2.2 读到请求的用户,调出他的id,在写回去
int id = dis.readInt();
IUserService service = new IUserServiceImpl();
User user = service.findUserById(id);

// 以下两行代码:客户端将user的id和name通过二进制写回去
dos.writeInt(user.getId());
dos.writeUTF(user.getName());

dos.flush();
}
}
  1. client 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Client {
public static void main(String[] args) throws Exception {
// 1.客户端以及包装二进制工具
Socket socket = new Socket("127.0.0.1",8088);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(123); // 注意这里是写死的,缺少灵活性

//2. 发送出要查询的id
socket.getOutputStream().write(baos.toByteArray());
socket.getOutputStream().flush();

//3. 接收服务端返回的结果
DataInputStream dis = new DataInputStream(socket.getInputStream());
int id = dis.readInt(); // 处理接收到的int
String name = dis.readUTF(); // 处理接收到的str
User user = new User(id,name);
System.out.println(user);
dos.close();
socket.close();
}
}
  1. 问题缺点所在
    • 代码是写死的,对象有多少个属性,那么这个代码修改起来就有多麻烦
    • 如果新增属性,业务代码和传输数据的代码是耦合的

1.2 rpc-02-网络封装

  • 省略了网络传输的部分(对网络部分进行封装)
    • 使用网络部分的代理 stub 对象
    • 静态代理
  1. stub : 其实就是网络部分放进一个类中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Stub {
public User findUserById(int id) throws IOException {
//1. 联网写出去
Socket socket = new Socket("127.0.0.1",8088);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(123); // 注意这里是写死的,缺少灵活性

//2. 发送出要查询的id
socket.getOutputStream().write(baos.toByteArray());
socket.getOutputStream().flush();

//3.接收服务端返回的结果
DataInputStream dis = new DataInputStream(socket.getInputStream());
int idtmp = dis.readInt();
if(idtmp != id) System.out.println("error");
String name = dis.readUTF();
User user = new User(id,name);
return user;
}
}
  1. client
1
2
3
4
5
6
public class Client {
public static void main(String[] args) throws IOException {
Stub stub = new Stub();
System.out.println(stub.findUserById(123));
}
}

1.3 rpc-03-动态代理

  • 对于变化的代码: 使用动态代理

    • Proxy :动态代理的类
    • InvocationHandler :处理对网络的请求
    • 基于动态代理生成代理对象,当调用代理对象的方法时,由代理进行相关信息(方法、参数等)的组装并发送到服务器进行远程调用,并由代理接收调用结果并返回。
    • JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用业务方法前调用InvocationHandler 处理。代理类必须实现 InvocationHandler 接口,并且,JDK 动态代理只能代理实现了接口的类
  • 使用动态代理的步骤

    1、编写需要被代理的类和接口

    2、编写代理类,需要实现 InvocationHandler 接口,重写 invoke() 方法;

    3、使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)动态创建代理类对象,通过代理类对象调用业务方法。

  • 动态代理代码demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
interface DemoInterface {
String hello(String msg);
}

class DemoImpl implements DemoInterface {
@Override
public String hello(String msg) {
System.out.println("msg = " + msg);
return "hello";
}
}

class DemoProxy implements InvocationHandler {

private DemoInterface service;

public DemoProxy(DemoInterface service) {
this.service = service;
}

@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("调用方法前...");
Object returnValue = method.invoke(service, args);
System.out.println("调用方法后...");
return returnValue;
}

}

public class Solution {
public static void main(String[] args) {
DemoProxy proxy = new DemoProxy(new DemoImpl());
DemoInterface service = Proxy.newInstance(
DemoInterface.class.getClassLoader(),
new Class<?>[]{DemoInterface.class},
proxy
);
System.out.println(service.hello("呀哈喽!"));
}
}
  1. stub : 代理类(屏蔽了对网络的细节)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Stub {
public static IUserService getStub(){
// 1. invocationHandler: 生成代理类
InvocationHandler h = new InvocationHandler() {
@Override
// proxy : 代理的类
// method : finduserbyid
// args : id = 123
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket("127.0.0.1",8088);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(123); // 注意这里是写死的,缺少灵活性
//发送出要查询的id
socket.getOutputStream().write(baos.toByteArray());
socket.getOutputStream().flush();
//接收服务端返回的结果
DataInputStream dis = new DataInputStream(socket.getInputStream());
int id = dis.readInt();
String name = dis.readUTF();
Object user = new User(id,name);

return user;
}
};

// 2. new 代理,注意三个参数
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
System.out.println(o.getClass().getName()); // 打印结果:Proxy
System.out.println(o.getClass().getInterfaces()[0]); // 打印结果:IUserService接口
return (IUserService) o;
}
}
  1. client : 客户端使用
1
2
3
4
5
6
7
public class Client {
public static void main(String[] args) {
// 1. 使用动态代理新产生的类
IUserService stub = Stub.getStub();
System.out.println(stub.findUserById(123));
}
}

1.4 rpc-04-动态代理通用代码

  1. stub : 代理 (方法类型和参数的更改)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ublic class Stub {
static IUserService getStub(){
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket("127.0.0.1",8088);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

// 通用化的改动 :
oos.writeUTF(method.getName()); // 调用方法的名称
oos.writeObject(method.getParameterTypes()); // 避免重载的存在,参数类型不同
oos.writeObject(args); // 调用方法的参数
oos.flush();

//接收服务端返回的结果,object读入
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
User user = (User)ois.readObject();

return user;
}
};

Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
System.out.println(o.getClass().getName());
System.out.println(o.getClass().getInterfaces()[0]);
return (IUserService) o;
}
  1. server 端 : 这里不像之前,也需要做出改动(对应客户端)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Server {
private static boolean running = true;

// 1. 建立链接
public static void main(String[] args) throws Exception {
ServerSocket server = new ServerSocket(8088);
while(running){
Socket client = server.accept();
process(client);
client.close();
}
server.close();
}

// 2. 处理客户端逻辑
public static void process(Socket socket) throws Exception {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

// 为了适应客户端通用化而做的改动
String methodName = ois.readUTF(); // 读取客户端传来调用的方法名字
Class[] parameterTypes = (Class[]) ois.readObject(); // 读取客户端调用的参数类型
Object[] parameters = (Object[]) ois.readObject(); // 读取客户端调用的方法参数

IUserService service = new IUserServiceImpl();//服务类型暂时还是写死的,不够灵活
Method method = service.getClass().getMethod(methodName, parameterTypes);
User user = (User)method.invoke(service, parameters);
oos.writeObject(user);
oos.flush();
}
}

1.5 rpc-05-处理不同客户请求

  1. 假设还有一个Product 对象以及Product 的业务类
  2. 这个实现类过程完全可以使用Spring注入!!
  3. 以下的rpc调用屏蔽了底层的网络的实现,也实现了服务端代码的复用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
int id;
String name;
int count;

public Product(int id, String name, int count) {
this.id = id;
this.name = name;
this.count = count;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

@Override
public String toString() {
return "Product{" +
"id=" + id +
", name=" + name +
", count=" + count +
'}';
}
}

public interface IProductService {
Product findProductByName(String name);
}

public class IProductServiceImpl implements IProductService{
@Override
public Product findProductByName(String name) {
return new Product(1,name,1);
}
}
  1. 客户端
1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
IUserService service = (IUserService) Stub.getStub(IUserService.class);
IProductService service2 = (IProductService)Stub.getStub(IProductService.class);
System.out.println(service.findUserById(123));
System.out.println(service2.findProductByName("Bob"));
}
}
  1. 代理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Stub {
static Object getStub(Class c){ // 注意这里传入的是Class
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket("127.0.0.1",8088);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

//添加了服务类型的传输
oos.writeUTF(c.getName());//服务类型
oos.writeUTF(method.getName());//方法名
oos.writeObject(method.getParameterTypes());//方法参数类型
oos.writeObject(args);//方法参数
oos.flush();

//接收服务端返回的结果,object读入
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Object obj = ois.readObject();

return obj;//改为返回通用对象
}
};

Object o = Proxy.newProxyInstance(c.getClassLoader(), new Class[]{c}, h);//这里要写成通用的c,而不是固定的接口
System.out.println(o.getClass().getName());
System.out.println(o.getClass().getInterfaces()[0]);
return o;
}
}
  1. 服务端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Server {
private static boolean running = true;
private static HashMap<String,Class> registerTable = new HashMap<>();
static{
registerTable.put(IUserService.class.getName(),IUserServiceImpl.class);//key类型是接口,value是具体实现类才能完成调用
registerTable.put(IProductService.class.getName(), IProductServiceImpl.class);
}
public static void main(String[] args) throws Exception {
ServerSocket server = new ServerSocket(8088);
while(running){
Socket client = server.accept();
process(client);
client.close();
}
server.close();
}
public static void process(Socket socket) throws Exception {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

//为了适应客户端通用化而做的改动
String clazzName = ois.readUTF();
String methodName = ois.readUTF();
Class[] parameterTypes = (Class[]) ois.readObject();
Object[] parameters = (Object[]) ois.readObject();

//IUserService service = new IUserServiceImpl();
//本来是硬编码new出来的,现在变成从注册表中查到服务类,如果使用spring甚至还可以直接根据配置注入bean然后根据bean查找。(@autowired)
Object service = registerTable.get(clazzName).newInstance();
Method method = service.getClass().getMethod(methodName, parameterTypes);
Object o = method.invoke(service, parameters);
oos.writeObject(o);
oos.flush();
}
}

2 序列化 Hessian使用

  1. 序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 这是讲object序列化
* @param obj
* @return
*/
public static byte[] serialization(Object obj){
if(obj==null){
throw new NullPointerException();
}
ByteArrayOutputStream os=new ByteArrayOutputStream();
HessianOutput output=new HessianOutput(os);
try {
output.writeObject(obj);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return os.toByteArray();
}
  1. 反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 这是反序列化
* @param by
* @return
*/
public static Object deserialize(byte[] by){
Object readObject=null;
if(by==null){
throw new NullPointerException();
}
ByteArrayInputStream ins=new ByteArrayInputStream(by);
HessianInput input=new HessianInput(ins);
try {
readObject = input.readObject();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return readObject;
}
  • 与JDK的序列化对比:hessian去掉了许多多余的信息,长度更短
  • 常用的序列化协议:protobuf , thift, hessian
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信