最近开发的一个项目,后端采用thrift框架来提供rpc服务(java语言实现),然后前端采用php语言来生成thrift client调用后台RPC服务。由于某些原因,上周我把thrift定义文件中一个struct名称修改了,当然也没多想,顺手就把java服务端重新编译部署,而php前端的部署未做任何变化,按常规理解,服务契约中的类名,从A改成B,服务的调用方理应同步更新部署,否则感觉应该会出错。
然而,美好的事情就这么发生了,一切运行正常,依旧丝丝顺滑!
再然后,我就开始思考人生,重新理解 thrift内部的序列化与反序列化机制,很快就想明白了,借用之前写过的博客rpc框架之 avro 学习 2 - 高效的序列化中的一张图:
thrift内部存储二进制数据时,为了提高存储效率,每个field都分配了一个数字编号,所以在序列化及反序列化时,其实是只认数字编号,不管名称的,这也正是thrift IDL文件定义struct时,为什么强制要求每个成员都要指定一个在struct本身范围内不重复的数字序号
struct PersonModel { 1: i16 age = 0, 2: string name, 3: bool sex, 4: double salary, 5: byte childrenCount}
IDL生成的具体语言的源代码中,解析对象时,同样也只看序号,以c#生成的代码为例:
1 public void Read (TProtocol iprot) 2 { 3 iprot.IncrementRecursionDepth(); 4 try 5 { 6 TField field; 7 iprot.ReadStructBegin(); 8 while (true) 9 { 10 field = iprot.ReadFieldBegin(); 11 if (field.Type == TType.Stop) { 12 break; 13 } 14 switch (field.ID) 15 { 16 case 1: 17 if (field.Type == TType.I16) { 18 Age = iprot.ReadI16(); 19 } else { 20 TProtocolUtil.Skip(iprot, field.Type); 21 } 22 break; 23 case 2: 24 if (field.Type == TType.String) { 25 Name = iprot.ReadString(); 26 } else { 27 TProtocolUtil.Skip(iprot, field.Type); 28 } 29 break; 30 case 3: 31 if (field.Type == TType.Bool) { 32 Sex = iprot.ReadBool(); 33 } else { 34 TProtocolUtil.Skip(iprot, field.Type); 35 } 36 break; 37 case 4: 38 if (field.Type == TType.Double) { 39 Salary = iprot.ReadDouble(); 40 } else { 41 TProtocolUtil.Skip(iprot, field.Type); 42 } 43 break; 44 case 5: 45 if (field.Type == TType.Byte) { 46 ChildrenCount = iprot.ReadByte(); 47 } else { 48 TProtocolUtil.Skip(iprot, field.Type); 49 } 50 break; 51 default: 52 TProtocolUtil.Skip(iprot, field.Type); 53 break; 54 } 55 iprot.ReadFieldEnd(); 56 } 57 iprot.ReadStructEnd(); 58 } 59 finally 60 { 61 iprot.DecrementRecursionDepth(); 62 } 63 }
从上面的case语句可以很清楚的看出,代码内部只认数字序号,不关心名称。
结论:只要不改变struct内部的成员类型和数字编号,struct对应的类名可以放心大胆的修改。