對於健壯的服務,其中的數據類型必須一種機制來對其進行版本管理,尤爲是它能夠在不中斷服務(或者更壞的狀況,出現段錯誤)的前提下,增長或刪除一個對象中的字段,或者改變一個函數的參數列表。java
字段標識符apache
Thrift的版本管理是經過字段標識符來實現的。對於每一個被Thrift編碼的結構的域頭,都有一個惟一的字段標識符,這個字段標識符和它的類型說明符構成了對這個字段獨一無二的識別。後端
Thrift定義語言支持字段標識符的自動分配,可是好的程序實踐中是明確指出字段標識符。字段標識符使用以下方式指定:安全
struct Example { 1:i32 number = 0, 2:i64 bigNumber, 3:double decimals, 4:string name = "thrifty" }
爲了不人工和自動分配的標識符衝突,忽略了標識符的字段自動從-1遞減分配字段標識符,而且Thrift定義語言只支持人工分配正的標識符。服務器
當數據正在被反序列化的時候,產生的代碼可以使用這些字段標識符來恰當的識別字段,並判斷這個標識符是否在它的定義文件中和一個字段對齊。若是一個字段標識符不被識別,產生的代碼能夠用類型說明符去跳過這個不可知的字段而不產生任何錯誤。一樣,這個也能夠歸結爲這樣一個事實:全部的數據類型都是自劃界的。app
字段標識符也可以在函數列表中被指定。事實上,參數列表在後端不只做爲結構被呈現,而是在編譯器前段分享了相同的代碼。這是爲了容許安全的修改方法的參數。ide
service StringCache { void set(1:i32 key, 2:string value), string get(1:i32 key) throws(1:KeyNotFound knf), void delete(1:i32 key) }
對於每一個結構,都選擇指定字段標識符的語法。結構能夠當作一個字典,標識符是主鍵,強類型名的字段是值。函數
Issetui
當遇到一個未指望的字段,可以被安全的忽略和丟棄。當一個預期的字段未被找到,必須有一些方法來告知開發者該字段未出現。這是經過內部結構isset來實現的,這個結構位於定義對象內部。本質上說,每一個thrift結構內部的isset對象爲每一個字段包含了一個布爾值,以此指示這個字段是否出如今結構中。當一個閱讀器接受一個結構,它應該在直接操做它以前檢查這個是否置位。this
以上面Example爲例,每一個字段都有一個isset方法,生成的Java代碼以下:
/** * Autogenerated by Thrift Compiler (0.11.0) * * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING * @generated */ @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) @javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-06-18") public class Example implements org.apache.thrift.TBase<Example, Example._Fields>, java.io.Serializable, Cloneable, Comparable<Example> { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("Example"); private static final org.apache.thrift.protocol.TField NUMBER_FIELD_DESC = new org.apache.thrift.protocol.TField("number", org.apache.thrift.protocol.TType.I32, (short)1); private static final org.apache.thrift.protocol.TField BIG_NUMBER_FIELD_DESC = new org.apache.thrift.protocol.TField("bigNumber", org.apache.thrift.protocol.TType.I64, (short)2); private static final org.apache.thrift.protocol.TField DECIMALS_FIELD_DESC = new org.apache.thrift.protocol.TField("decimals", org.apache.thrift.protocol.TType.DOUBLE, (short)3); private static final org.apache.thrift.protocol.TField NAME_FIELD_DESC = new org.apache.thrift.protocol.TField("name", org.apache.thrift.protocol.TType.STRING, (short)4); private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new ExampleStandardSchemeFactory(); private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new ExampleTupleSchemeFactory(); public int number; // required public long bigNumber; // required public double decimals; // required public java.lang.String name; // required /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ public enum _Fields implements org.apache.thrift.TFieldIdEnum { NUMBER((short)1, "number"), BIG_NUMBER((short)2, "bigNumber"), DECIMALS((short)3, "decimals"), NAME((short)4, "name"); private static final java.util.Map<java.lang.String, _Fields> byName = new java.util.HashMap<java.lang.String, _Fields>(); static { for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { byName.put(field.getFieldName(), field); } } /** * Find the _Fields constant that matches fieldId, or null if its not found. */ public static _Fields findByThriftId(int fieldId) { switch(fieldId) { case 1: // NUMBER return NUMBER; case 2: // BIG_NUMBER return BIG_NUMBER; case 3: // DECIMALS return DECIMALS; case 4: // NAME return NAME; default: return null; } } /** * Find the _Fields constant that matches fieldId, throwing an exception * if it is not found. */ public static _Fields findByThriftIdOrThrow(int fieldId) { _Fields fields = findByThriftId(fieldId); if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); return fields; } /** * Find the _Fields constant that matches name, or null if its not found. */ public static _Fields findByName(java.lang.String name) { return byName.get(name); } private final short _thriftId; private final java.lang.String _fieldName; _Fields(short thriftId, java.lang.String fieldName) { _thriftId = thriftId; _fieldName = fieldName; } public short getThriftFieldId() { return _thriftId; } public java.lang.String getFieldName() { return _fieldName; } } // isset id assignments private static final int __NUMBER_ISSET_ID = 0; private static final int __BIGNUMBER_ISSET_ID = 1; private static final int __DECIMALS_ISSET_ID = 2; private byte __isset_bitfield = 0; public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; static { java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); tmpMap.put(_Fields.NUMBER, new org.apache.thrift.meta_data.FieldMetaData("number", org.apache.thrift.TFieldRequirementType.DEFAULT, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I32))); tmpMap.put(_Fields.BIG_NUMBER, new org.apache.thrift.meta_data.FieldMetaData("bigNumber", org.apache.thrift.TFieldRequirementType.DEFAULT, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64))); tmpMap.put(_Fields.DECIMALS, new org.apache.thrift.meta_data.FieldMetaData("decimals", org.apache.thrift.TFieldRequirementType.DEFAULT, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.DOUBLE))); tmpMap.put(_Fields.NAME, new org.apache.thrift.meta_data.FieldMetaData("name", org.apache.thrift.TFieldRequirementType.DEFAULT, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING))); metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(Example.class, metaDataMap); } public Example() { this.number = 0; this.name = "thrifty"; } public Example( int number, long bigNumber, double decimals, java.lang.String name) { this(); this.number = number; setNumberIsSet(true); this.bigNumber = bigNumber; setBigNumberIsSet(true); this.decimals = decimals; setDecimalsIsSet(true); this.name = name; } /** * Performs a deep copy on <i>other</i>. */ public Example(Example other) { __isset_bitfield = other.__isset_bitfield; this.number = other.number; this.bigNumber = other.bigNumber; this.decimals = other.decimals; if (other.isSetName()) { this.name = other.name; } } public Example deepCopy() { return new Example(this); } @Override public void clear() { this.number = 0; setBigNumberIsSet(false); this.bigNumber = 0; setDecimalsIsSet(false); this.decimals = 0.0; this.name = "thrifty"; } public int getNumber() { return this.number; } public Example setNumber(int number) { this.number = number; setNumberIsSet(true); return this; } public void unsetNumber() { __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __NUMBER_ISSET_ID); } /** Returns true if field number is set (has been assigned a value) and false otherwise */ public boolean isSetNumber() { return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __NUMBER_ISSET_ID); } public void setNumberIsSet(boolean value) { __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __NUMBER_ISSET_ID, value); } public long getBigNumber() { return this.bigNumber; } public Example setBigNumber(long bigNumber) { this.bigNumber = bigNumber; setBigNumberIsSet(true); return this; } public void unsetBigNumber() { __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __BIGNUMBER_ISSET_ID); } /** Returns true if field bigNumber is set (has been assigned a value) and false otherwise */ public boolean isSetBigNumber() { return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __BIGNUMBER_ISSET_ID); } public void setBigNumberIsSet(boolean value) { __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __BIGNUMBER_ISSET_ID, value); } public double getDecimals() { return this.decimals; } public Example setDecimals(double decimals) { this.decimals = decimals; setDecimalsIsSet(true); return this; } public void unsetDecimals() { __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __DECIMALS_ISSET_ID); } /** Returns true if field decimals is set (has been assigned a value) and false otherwise */ public boolean isSetDecimals() { return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __DECIMALS_ISSET_ID); } public void setDecimalsIsSet(boolean value) { __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __DECIMALS_ISSET_ID, value); } public java.lang.String getName() { return this.name; } public Example setName(java.lang.String name) { this.name = name; return this; } public void unsetName() { this.name = null; } /** Returns true if field name is set (has been assigned a value) and false otherwise */ public boolean isSetName() { return this.name != null; } public void setNameIsSet(boolean value) { if (!value) { this.name = null; } } public void setFieldValue(_Fields field, java.lang.Object value) { switch (field) { case NUMBER: if (value == null) { unsetNumber(); } else { setNumber((java.lang.Integer)value); } break; case BIG_NUMBER: if (value == null) { unsetBigNumber(); } else { setBigNumber((java.lang.Long)value); } break; case DECIMALS: if (value == null) { unsetDecimals(); } else { setDecimals((java.lang.Double)value); } break; case NAME: if (value == null) { unsetName(); } else { setName((java.lang.String)value); } break; } } public java.lang.Object getFieldValue(_Fields field) { switch (field) { case NUMBER: return getNumber(); case BIG_NUMBER: return getBigNumber(); case DECIMALS: return getDecimals(); case NAME: return getName(); } throw new java.lang.IllegalStateException(); } /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ public boolean isSet(_Fields field) { if (field == null) { throw new java.lang.IllegalArgumentException(); } switch (field) { case NUMBER: return isSetNumber(); case BIG_NUMBER: return isSetBigNumber(); case DECIMALS: return isSetDecimals(); case NAME: return isSetName(); } throw new java.lang.IllegalStateException(); } @Override public boolean equals(java.lang.Object that) { if (that == null) return false; if (that instanceof Example) return this.equals((Example)that); return false; } public boolean equals(Example that) { if (that == null) return false; if (this == that) return true; boolean this_present_number = true; boolean that_present_number = true; if (this_present_number || that_present_number) { if (!(this_present_number && that_present_number)) return false; if (this.number != that.number) return false; } boolean this_present_bigNumber = true; boolean that_present_bigNumber = true; if (this_present_bigNumber || that_present_bigNumber) { if (!(this_present_bigNumber && that_present_bigNumber)) return false; if (this.bigNumber != that.bigNumber) return false; } boolean this_present_decimals = true; boolean that_present_decimals = true; if (this_present_decimals || that_present_decimals) { if (!(this_present_decimals && that_present_decimals)) return false; if (this.decimals != that.decimals) return false; } boolean this_present_name = true && this.isSetName(); boolean that_present_name = true && that.isSetName(); if (this_present_name || that_present_name) { if (!(this_present_name && that_present_name)) return false; if (!this.name.equals(that.name)) return false; } return true; } @Override public int hashCode() { int hashCode = 1; hashCode = hashCode * 8191 + number; hashCode = hashCode * 8191 + org.apache.thrift.TBaseHelper.hashCode(bigNumber); hashCode = hashCode * 8191 + org.apache.thrift.TBaseHelper.hashCode(decimals); hashCode = hashCode * 8191 + ((isSetName()) ? 131071 : 524287); if (isSetName()) hashCode = hashCode * 8191 + name.hashCode(); return hashCode; } @Override public int compareTo(Example other) { if (!getClass().equals(other.getClass())) { return getClass().getName().compareTo(other.getClass().getName()); } int lastComparison = 0; lastComparison = java.lang.Boolean.valueOf(isSetNumber()).compareTo(other.isSetNumber()); if (lastComparison != 0) { return lastComparison; } if (isSetNumber()) { lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.number, other.number); if (lastComparison != 0) { return lastComparison; } } lastComparison = java.lang.Boolean.valueOf(isSetBigNumber()).compareTo(other.isSetBigNumber()); if (lastComparison != 0) { return lastComparison; } if (isSetBigNumber()) { lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.bigNumber, other.bigNumber); if (lastComparison != 0) { return lastComparison; } } lastComparison = java.lang.Boolean.valueOf(isSetDecimals()).compareTo(other.isSetDecimals()); if (lastComparison != 0) { return lastComparison; } if (isSetDecimals()) { lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.decimals, other.decimals); if (lastComparison != 0) { return lastComparison; } } lastComparison = java.lang.Boolean.valueOf(isSetName()).compareTo(other.isSetName()); if (lastComparison != 0) { return lastComparison; } if (isSetName()) { lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.name, other.name); if (lastComparison != 0) { return lastComparison; } } return 0; } public _Fields fieldForId(int fieldId) { return _Fields.findByThriftId(fieldId); } public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { scheme(iprot).read(iprot, this); } public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { scheme(oprot).write(oprot, this); } @Override public java.lang.String toString() { java.lang.StringBuilder sb = new java.lang.StringBuilder("Example("); boolean first = true; sb.append("number:"); sb.append(this.number); first = false; if (!first) sb.append(", "); sb.append("bigNumber:"); sb.append(this.bigNumber); first = false; if (!first) sb.append(", "); sb.append("decimals:"); sb.append(this.decimals); first = false; if (!first) sb.append(", "); sb.append("name:"); if (this.name == null) { sb.append("null"); } else { sb.append(this.name); } first = false; sb.append(")"); return sb.toString(); } public void validate() throws org.apache.thrift.TException { // check for required fields // check for sub-struct validity } private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { try { write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); } catch (org.apache.thrift.TException te) { throw new java.io.IOException(te); } } private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { try { // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor. __isset_bitfield = 0; read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); } catch (org.apache.thrift.TException te) { throw new java.io.IOException(te); } } private static class ExampleStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { public ExampleStandardScheme getScheme() { return new ExampleStandardScheme(); } } private static class ExampleStandardScheme extends org.apache.thrift.scheme.StandardScheme<Example> { public void read(org.apache.thrift.protocol.TProtocol iprot, Example 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: // NUMBER if (schemeField.type == org.apache.thrift.protocol.TType.I32) { struct.number = iprot.readI32(); struct.setNumberIsSet(true); } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; case 2: // BIG_NUMBER if (schemeField.type == org.apache.thrift.protocol.TType.I64) { struct.bigNumber = iprot.readI64(); struct.setBigNumberIsSet(true); } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; case 3: // DECIMALS if (schemeField.type == org.apache.thrift.protocol.TType.DOUBLE) { struct.decimals = iprot.readDouble(); struct.setDecimalsIsSet(true); } else { org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; case 4: // NAME if (schemeField.type == org.apache.thrift.protocol.TType.STRING) { struct.name = iprot.readString(); struct.setNameIsSet(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 struct.validate(); } public void write(org.apache.thrift.protocol.TProtocol oprot, Example struct) throws org.apache.thrift.TException { struct.validate(); oprot.writeStructBegin(STRUCT_DESC); oprot.writeFieldBegin(NUMBER_FIELD_DESC); oprot.writeI32(struct.number); oprot.writeFieldEnd(); oprot.writeFieldBegin(BIG_NUMBER_FIELD_DESC); oprot.writeI64(struct.bigNumber); oprot.writeFieldEnd(); oprot.writeFieldBegin(DECIMALS_FIELD_DESC); oprot.writeDouble(struct.decimals); oprot.writeFieldEnd(); if (struct.name != null) { oprot.writeFieldBegin(NAME_FIELD_DESC); oprot.writeString(struct.name); oprot.writeFieldEnd(); } oprot.writeFieldStop(); oprot.writeStructEnd(); } } private static class ExampleTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { public ExampleTupleScheme getScheme() { return new ExampleTupleScheme(); } } private static class ExampleTupleScheme extends org.apache.thrift.scheme.TupleScheme<Example> { @Override public void write(org.apache.thrift.protocol.TProtocol prot, Example struct) throws org.apache.thrift.TException { org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; java.util.BitSet optionals = new java.util.BitSet(); if (struct.isSetNumber()) { optionals.set(0); } if (struct.isSetBigNumber()) { optionals.set(1); } if (struct.isSetDecimals()) { optionals.set(2); } if (struct.isSetName()) { optionals.set(3); } oprot.writeBitSet(optionals, 4); if (struct.isSetNumber()) { oprot.writeI32(struct.number); } if (struct.isSetBigNumber()) { oprot.writeI64(struct.bigNumber); } if (struct.isSetDecimals()) { oprot.writeDouble(struct.decimals); } if (struct.isSetName()) { oprot.writeString(struct.name); } } @Override public void read(org.apache.thrift.protocol.TProtocol prot, Example struct) throws org.apache.thrift.TException { org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; java.util.BitSet incoming = iprot.readBitSet(4); if (incoming.get(0)) { struct.number = iprot.readI32(); struct.setNumberIsSet(true); } if (incoming.get(1)) { struct.bigNumber = iprot.readI64(); struct.setBigNumberIsSet(true); } if (incoming.get(2)) { struct.decimals = iprot.readDouble(); struct.setDecimalsIsSet(true); } if (incoming.get(3)) { struct.name = iprot.readString(); struct.setNameIsSet(true); } } } private static <S extends org.apache.thrift.scheme.IScheme> S scheme(org.apache.thrift.protocol.TProtocol proto) { return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); } }
案例分析:
版本不匹配可能發生在以下四種狀況中:
1.已添加字段,舊客戶端,新服務器
舊客戶端沒有發送新字段,新服務器識別到那個新字段未置位,執行對於舊數據請求的默認操做。
2.已刪除字段,舊客戶端,新服務器
舊客戶端發送已被刪除的字段,新服務器簡單忽略這個字段。
3.已添加字段,新客戶端,舊服務器
新客戶端發送了舊服務器不能識別的字段,舊服務器簡單的忽略,並按正常請求處理。
4.已刪除字段,新客戶端,舊服務器
最危險的狀況,對於丟失的字段,舊服務器不大可能有默認的合適動做執行。這種狀況,推薦在升級客戶端以前升級服務器端。