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

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

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

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

一个简单的Thrift文件

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

执行

  1. thrift -gen java Test.thrift

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

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

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

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

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

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

  1. public void writeStructBegin(TStruct struct) {
  2. }

即什么都没有做,接下来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. public void writeFieldBegin(TField field) throws TException {
  2. this.writeByte(field.type);
  3. this.writeI16(field.id);
  4. }

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

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

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

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

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

  1. public void writeFieldEnd() {
  2. }

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

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

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

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

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

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

  1. public void read(org.apache.thrift.protocol.TProtocol iprot, Test struct) throws org.apache.thrift.TException {
  2. org.apache.thrift.protocol.TField schemeField;
  3. iprot.readStructBegin();
  4. while (true)
  5. {
  6. schemeField = iprot.readFieldBegin();
  7. if (schemeField.type == org.apache.thrift.protocol.TType.STOP) {
  8. break;
  9. }
  10. switch (schemeField.id) {
  11. case 1: // KEY
  12. if (schemeField.type == org.apache.thrift.protocol.TType.I32) {
  13. struct.key = iprot.readI32();
  14. struct.setKeyIsSet(true);
  15. } else {
  16. org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
  17. }
  18. break;
  19. case 2: // VALUE
  20. if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
  21. struct.value = iprot.readString();
  22. struct.setValueIsSet(true);
  23. } else {
  24. org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
  25. }
  26. break;
  27. default:
  28. org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
  29. }
  30. iprot.readFieldEnd();
  31. }
  32. iprot.readStructEnd();
  33.  
  34. // check for required fields of primitive type, which can't be checked in the validate method
  35. if (!struct.isSetKey()) {
  36. throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not found in serialized data! Struct: " + toString());
  37. }
  38. struct.validate();
  39. }

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

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

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

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

构造完了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序列化与反序列化的实现机制分析 Thrift是如何实现序死化与反序列化的,在IDL文件中,更改IDL文件中的变量序号或者[使用默认序号的情况下,新增变量时,将新增的变量不放在IDL文件的结 ...

  2. .Net中的序列化和反序列化详解

    序列化通俗地讲就是将一个对象转换成一个字节流的过程,这样就可以轻松保存在磁盘文件或数据库中.反序列化是序列化的逆过程,就是将一个字节流转换回原来的对象的过程. 然而为什么需要序列化和反序列化这样的机制 ...

  3. Java 序列化和反序列化(三)Serializable 源码分析 - 2

    目录 Java 序列化和反序列化(三)Serializable 源码分析 - 2 1. ObjectStreamField 1.1 数据结构 1.2 构造函数 2. ObjectStreamClass ...

  4. Java 序列化和反序列化(二)Serializable 源码分析 - 1

    目录 Java 序列化和反序列化(二)Serializable 源码分析 - 1 1. Java 序列化接口 2. ObjectOutputStream 源码分析 2.1 ObjectOutputSt ...

  5. 序列化与反序列化、def的介绍与快速使用、cbv源码分析、APIView与request对象分析

    今日内容概要 序列化与反序列化 def介绍和快速使用 cbv源码流程分析 drf之APIView和Request对象分析 内容详细 1.序列化和反序列化 # api接口开发 最核心最常见的一个过程就是 ...

  6. 【Java基础】序列化与反序列化深入分析

    一.前言 复习Java基础知识点的序列化与反序列化过程,整理了如下学习笔记. 二.为什么需要序列化与反序列化 程序运行时,只要需要,对象可以一直存在,并且我们可以随时访问对象的一些状态信息,如果程序终 ...

  7. Java反序列化漏洞通用利用分析

    原文:http://blog.chaitin.com/2015-11-11_java_unserialize_rce/ 博主也是JAVA的,也研究安全,所以认为这个漏洞非常严重.长亭科技分析的非常细致 ...

  8. C#: .net序列化及反序列化 [XmlElement(“节点名称”)]

    .net序列化及反序列化 序列化是指一个对象的实例可以被保存,保存成一个二进制串,当然,一旦被保存成二进制串,那么也可以保存成文本串了.比如,一个计数器,数值为2,我们可以用字符串“2”表示.如果有个 ...

  9. 序列化、反序列化和transient关键字的作用

    引言 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口, ...

随机推荐

  1. Linux笔记(五) - 用户管理命令

    (1)添加用户:useradd [选项] 用户 -u UID:手工指定用户的UID号-d 家目录:手工指定用户的家目录-c 用户说明:手工指定用户说明,有空格需加双引号-g 初始组:手工指定初始组-G ...

  2. 大咖,我能转行做UX设计师吗?

    前几天,有个朋友找到我,叫我给分析下他适不适合转UX设计.他的专业是建筑设计,之所以要辞职,也就是公司破事多,老板又不看重他.看到UX设计这个行业的前景很不错,想要转行.他说的也没错, 现在的UX设计 ...

  3. 关于OI本地简易评测姬3.0发布的通知

    本辣鸡蒟蒻的OI本地评测姬3.0出炉辣.[由wjc大蒟蒻编写,rxb神犇秒秒钟搞出编译器命令行,解决了评测姬编译一大难关并便携化,也为评测姬设计提出了宝贵的建议],目前支持pas和cpp(本辣鸡错了, ...

  4. java_XML_SAX

    用SAX解析XML采用的是从上而下的基于事件驱动的解析方式,在解析过程中会视情况自动调用startDocument().startElement().characters().endElement() ...

  5. POJ 1308 Is It A Tree?--题解报告

    Is It A Tree? Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 31092   Accepted: 10549 D ...

  6. C-Swipe Mobile 一个适用于Vue2.x的移动端轮播组件

    近期在做的一个Vue2项目里需要一个可以滑动的轮播组件,但是又因为现有的传统轮播库功能过于繁琐和笨重.因此自己写了一个针对于Vue2.x的轻型轮播组件. 项目GitHub链接:C-Swipe Mobi ...

  7. 全球分布式数据库:Google Spanner(论文翻译)

    本文由厦门大学计算机系教师林子雨翻译,翻译质量很高,本人只对极少数翻译得不太恰当的地方进行了修改. [摘要]:Spanner 是谷歌公司研发的.可扩展的.多版本.全球分布式.同步复制数据库.它是第一个 ...

  8. Swift 2.0 单例的用法

    单例我们项目中是很常用的,今天刚学了在swift中怎么写单例和调用单例.下面我们简单的介绍一下.我们先看看Swift单例的写法: import UIKit class Shareinstance: N ...

  9. DB2 表空间监控

    默认DB2 缓冲池信息监控是OFF, 需要开启(DB2表空间是由缓冲池分配的) CollBufferpool : ============ The CollBufferpool collector c ...

  10. Jquery AutoComplete实现搜索自动完成

    AutoComplete控件就是指用户在文本框输入前几个字母或是汉字的时候,该控件就能从存放数据的文本或是数据库里将所有以这些字母开头的数据提示给用户,供用户选择,提供方便. 例子: <!doc ...