Thrift序列化与反序列化的实现机制分析

Thrift是如何实现序死化与反序列化的,在IDL文件中,更改IDL文件中的变量序号或者[使用默认序号的情况下,新增变量时,将新增的变量不放在IDL文件的结尾,均会导致Thrift文件的反序列后无法做到向后兼容],我们只有理解Thrift是如何实现序列化的,才能了解这种现象产生的原因,才能把代码写的更让人放心

关于Thrift域的版本号的定义可以在http://thrift.apache.org/static/files/thrift-20070401.pdf这篇文章中找到说定义

1
2
3
4
5
6
7
8
Versioning in Thrift is implemented via field identifiers.
The field header for every member of a struct in Thrift is
encoded with a unique field identifier. The combination of
this field identifier and its type specifier is used to
uniquely identify the field. The Thrift definition language
supports automatic assignment of field identifiers,
but it is good programming practice to always explicitly
specify field identifiers.

翻译过来,大概意思就是Thrift中每个域都有一个版本号,这个版本号是由属性的数字序号 + 属性的类型来确定的

一个简单的Thrift文件

1
2
3
4
struct Test {
    1 : required i32 key;
    2 : required string value;
}

执行

1
thrift -gen java Test.thrift

将thrift文件转换成java源文件,在此不列出详细的源文件内容,只列出与序列化与反序列化相关的代码

序列化,实际上就是write,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//http://www.aiprograming.com/b/pengpeng/24<br>public void write(org.apache.thrift.protocol.TProtocol oprot, Test struct) throws org.apache.thrift.TException {
  struct.validate();
  
  oprot.writeStructBegin(STRUCT_DESC);
  oprot.writeFieldBegin(KEY_FIELD_DESC);
  oprot.writeI32(struct.key);
  oprot.writeFieldEnd();
  if (struct.value != null) {
    oprot.writeFieldBegin(VALUE_FIELD_DESC);
    oprot.writeString(struct.value);
    oprot.writeFieldEnd();
  }
  oprot.writeFieldStop();
  oprot.writeStructEnd();
}

struct.validate()主要用来校验thrift文件中定义的required域即必传的值是不是有值,没有值就会抛出TProtocolException异常

1
2
3
4
5
6
7
public void validate() throws org.apache.thrift.TException {
  // check for required fields
  // alas, we cannot check 'key' because it's a primitive and you chose the non-beans generator.
  if (value == null) {
    throw new org.apache.thrift.protocol.TProtocolException("Required field 'value' was not present! Struct: " + toString());
  }
}

oprot.writeStructBegin(STRUCT_DESC);STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Test");即开始写结构体的标识,在这里我们以TBinaryProtocol二进制 的传输作为例子,TBinaryProtocol中writeStructBegin的实现如下

1
2
public void writeStructBegin(TStruct struct) {
}

即什么都没有做,接下来oprot.writeFieldBegin(KEY_FIELD_DESC);中

KEY_FIELD_DESC = new org.apache.thrift.protocol.TField("key", org.apache.thrift.protocol.TType.I32, (short)1);

TBinaryProtocol中对应的实现如下

1
2
3
4
public void writeFieldBegin(TField field) throws TException {
    this.writeByte(field.type);
    this.writeI16(field.id);
}

从上面的代码中可以看出序列化的过程中写入的是域的类型以及域的数字序号,从org.apache.thrift.protocol.TType中,我们也可以知道在thrift IDL支持的数据类型,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final class TType {
    public static final byte STOP = 0;
    public static final byte VOID = 1;
    public static final byte BOOL = 2;
    public static final byte BYTE = 3;
    public static final byte DOUBLE = 4;
    public static final byte I16 = 6;
    public static final byte I32 = 8;
    public static final byte I64 = 10;
    public static final byte STRING = 11;
    public static final byte STRUCT = 12;
    public static final byte MAP = 13;
    public static final byte SET = 14;
    public static final byte LIST = 15;
    public static final byte ENUM = 16;
  
    public TType() {
    }

其中STOP用于序列化完所有的域后,写入序死化文件,表示所有的域都序列化完成,接下来是oprot.writeI32(struct.key);这条语句就是写入要序列化的int类型值,对应TBinaryProtocol的实现如下所示:

1
2
3
4
5
6
7
public void writeI32(int i32) throws TException {
    this.i32out[0] = (byte)(255 & i32 >> 24);
    this.i32out[1] = (byte)(255 & i32 >> 16);
    this.i32out[2] = (byte)(255 & i32 >> 8);
    this.i32out[3] = (byte)(255 & i32);
    this.trans_.write(this.i32out, 0, 4);
}

大致意思就是将int转换为byte数组,写入下层的channel中,接下来就是oprot.writeFieldEnd();对应TBinaryProtocol的实现如下所示:

public void writeFieldEnd() {
}

接下来的这段代应就是序列化Test.thrift中定义的value,和上面的序列化过程基本类似,但是也有区别,在序列化string类型时,会先在序死化文件里写入字符串的长度,然后再写入字符串的值

1
2
3
4
5
if (struct.value != null) {
  oprot.writeFieldBegin(VALUE_FIELD_DESC);
  oprot.writeString(struct.value);
  oprot.writeFieldEnd();
}

最后,会向序列化的文件里面写入一个字节的0表示序列化结束,如下所示

1
2
3
public void writeFieldStop() throws TException {
    this.writeByte((byte)0);
}

从上面的序列化过程中,我们可以知道序列化后的文件里面只有域的类型以及域的数字序号,没有域的名称,因此与JSON/XML这种序列化工具相比,thrift序列化后生成的文件体积要小很多

有了序列化的生成过程,再来看看thrift是如何反序列化,就非常简单了,反序列化的代码如下所示

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
public void read(org.apache.thrift.protocol.TProtocol iprot, Test struct) throws org.apache.thrift.TException {
  org.apache.thrift.protocol.TField schemeField;
  iprot.readStructBegin();
  while (true)
  {
    schemeField = iprot.readFieldBegin();
    if (schemeField.type == org.apache.thrift.protocol.TType.STOP) {
      break;
    }
    switch (schemeField.id) {
      case 1: // KEY
        if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
          struct.key = iprot.readI32();
          struct.setKeyIsSet(true);
        else {
          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
        }
        break;
      case 2: // VALUE
        if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
          struct.value = iprot.readString();
          struct.setValueIsSet(true);
        else {
          org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
        }
        break;
      default:
        org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
    }
    iprot.readFieldEnd();
  }
  iprot.readStructEnd();
  
  // check for required fields of primitive type, which can't be checked in the validate method
  if (!struct.isSetKey()) {
    throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not found in serialized data! Struct: " + toString());
  }
  struct.validate();
}

反序列化最为核心的代码在while循环这里,schemeField是由域的类型type及域的数字序号id构成的一个类,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TField {
    public final String name;
    public final byte type;
    public final short id;
  
    public TField() {
        this("", (byte)0, (short)0);
    }
  
    public TField(String n, byte t, short i) {
        this.name = n;
        this.type = t;
        this.id = i;
    }
  
    public String toString() {
        return "<TField name:\'" this.name + "\' type:" this.type + " field-id:" this.id + ">";
    }
  
    public boolean equals(TField otherField) {
        return this.type == otherField.type && this.id == otherField.id;
    }
}

iprot.readFieldBegin();就是从序列化文件中构造一个TField类型的对象,TBinaryProtocol的实现如下所示,从下面的源代码可以看出,首先读取域的类型,然后读取域的数字序号

1
2
3
4
5
public TField readFieldBegin() throws TException {
    byte type = this.readByte();
    short id = type == 0?0:this.readI16();
    return new TField("", type, id);
}

构造完了TFiled对象之后,我们需要读取域的值,看switch语句,也很容易理解,要读取域的值,需要两个前提

1.域的数字序号相同

2.域的类型相同

在满足上面的两个要求的前提下,再根据域的类型,调用相应的读取方法,如果域的数字序号相同,但是域的类型不同,则会跳过给该域赋值,执行的代码逻辑是

1
org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);

最后,反序列化完成后,还要需检查一下必传的值是否已经传了,调用下面这段代码

1
struct.validate();

由反序列化的过程,可以知道,Thrift的反序列化,没有用到java的反射技术,也没有开设过多的内存空间,因此同JSON/XML相比,反序列化更快,更省内存,从反序列化的过程中,我们可以看到

Thrift的向后兼容性,需要满足一定的条件

1.域的数字序号不能改变

2.域的类型不能改变

满足了上面的两点,无论你增加还是删除域,都可以实现向后兼容,勿需担心 

Thrift序列化与反序列化的更多相关文章

  1. Thrift序列化与反序列化的实现机制分析

    Thrift是如何实现序死化与反序列化的,在IDL文件中,更改IDL文件中的变量序号或者[使用默认序号的情况下,新增变量时,将新增的变量不放在IDL文件的结尾,均会导致Thrift文件的反序列后无法做 ...

  2. 大型分布式C++框架《三:序列化与反序列化》

    一.前言  个人感觉序列化简单来说就是按一定规则组包.反序列化就是按组包时的规则来接包.正常来说.序列化不会很难.不会很复杂.因为过于复杂的序列化协议会导致较长的解析时间,这可能会使得序列化和反序列化 ...

  3. Hive之序列化与反序列化(SerDe)

    序列化与反序列化的作用 1,序列化是对象转化为字节序列的过程: 2,反序列化是字节码恢复为对象的过程: 序列化的作用主要有两个: (1),对象向的持久化:即把对象转换成字节码后保存文件: (2),对象 ...

  4. Django restful Framework 之序列化与反序列化

    1. 首先在已建好的工程目录下新建app命名为snippets,并将snippets app以及rest_framework app加到工程目录的 INSTALLED_APPS 中去,具体如下: IN ...

  5. 关于thrift的一些探索——thrift序列化技术

    thrift的IDL,相当于一个钥匙.而thrift传输过程,相当于从两个房间之间的传输数据. 图-1 (因为Thrift采用了C/S模型,不支持双向通信:client只能远程调用server端的RP ...

  6. Hessian 序列化和反序列化实现

    先聊聊 Java的序列化,Java官方的序列化和反序列化的实现被太多人吐槽,这得归于Java官方序列化实现的方式. 1.Java序列化的性能经常被吐槽.2.Java官方的序列化后的数据相对于一些优秀的 ...

  7. 6. kafka序列化和反序列化

    https://blog.csdn.net/weixin_33690963/article/details/91698279 kafka序列化: 生产者在将消息传入kafka之前需要将其序列化成byt ...

  8. hive序列化和反序列化serde

    一.简介 SerDe是Serializer/Deserializer的缩写.SerDe允许Hive读取表中的数据,并将其以任何自定义格式写回HDFS. 任何人都可以为自己的数据格式编写自己的SerDe ...

  9. Thrift RPC实战(三) thrift序列化揭秘

    本文主要讲解Thrift的序列化机制, 看看thrift作为数据交换格式是如何工作的? 1.构造应用场景: 1). 首先我们先来定义下thrift的简单结构. 1 2 3 4 5 namespace ...

随机推荐

  1. Cs231n课堂内容记录-Lecture 3 最优化

    Lecture 4 最优化 课程内容记录: (上)https://zhuanlan.zhihu.com/p/21360434?refer=intelligentunit (下)https://zhua ...

  2. March 05th, 2018 Week 10th Monday

    Fortune favors the bold. 勇者天佑. It has been increasingly apparent that courage is the main quality we ...

  3. [福大软工] Z班 团队Beta阶段成绩汇总

    Beta敏捷冲刺得分 队伍名 1 2 3 4 5 总分 Dipper 10 10 10 10 10 50 SWSD 9 9 9 9 7 43 五成胜算 10 10 10 10 10 50 人月神教 0 ...

  4. python六十五课——单元测试(一)

    对函数(模块中的)进行函数测试定义两个需要被测试的函数: #求和函数 def mySum(x,y): return x+y #相减函数 def mySub(x,y): return x-y print ...

  5. 【COCOS2DX-游戏开发之三一】之 坐标系(下) convertToNodeSpace和convertToWorldSpace

    游戏中常常会用到一些变换: 游戏中武器和角色在一个layer上,为了效率.会考虑将bullet, effect和 PhysicsParticle分别放到不用的层上,相应的层上使用batchnode来提 ...

  6. 关于HashMap的一些深入探索与理解

    在java中.大家差点儿是离不开对集合的使用的,像Map系列,List系列.Set系列.可是非常多人没有了解过或者研究过这些集合类究竟能够用在什么地方,而且有什么注意的地方.因此本文分Map系列和Li ...

  7. [ZJOI2012]灾难

    嘟嘟嘟 偶尔翻到的一道题. 50分暴力很好想,对于每一个点进行一次拓扑排序,然后每一次别memset,只清空走过的点,能拿到70分. 正解好像也挺好想,是一个叫"灭绝树"的东西. ...

  8. 转://创建oracle索引时需要注意的7个事项

    在创建Oracle索引时,有一些问题使我们需要注意的,下面就为您介绍创建oracle索引的一些注意事项,希望对您学习创建Oracle索引方面能有所帮助. 1.一般来说,不需要为比较小的表创建索引: 2 ...

  9. mybatis 常用

    1.新增时获得主键 <insert ...> <selectKey resultType="java.lang.Integer" keyProperty=&quo ...

  10. 摒弃FORM表单上传图片,异步批量上传照片

    之前作图像处理一直在用form表单做图片数据传输, 个人感觉low到爆炸而且用户体验极差,现在介绍一个一部批量上传图片的小技巧,忘帮助他人的同时也警醒自己在代码的编写时不要只顾着方便,也要考虑代码的健 ...