Подробное объяснение серии Apache Thrift (3) — механизм сериализации

Apache Thrift

предисловие

Thriftслужба поддержкибинарный,Формат сжатия,а такжеjsonформатировать данныеСериализацияидесериализовать. Разработчики могут более гибко выбирать конкретную форму протокола. возможен договорБесплатное расширениеДа, новая версия протокола, полностьюсовместимыйстарая версия!

текст

Введение в форматы обмена данными

в настоящее время популярныйформат обмена даннымиЕго можно разделить на следующие категории:

(1) Самоаналитический тип

Сериализованные данные содержатвсеструктура, в том числеfieldназваниеиvalueценность. Напримерxml/json/java serizable, Байдуmcpack/compack, относятся к этой категории. которые регулируют различные свойстваприказправильносериализовать/десериализоватьБез влияния.

(2) Полуаналитический тип

Сериализованные данные, отбрасывая некоторую информацию, такую ​​какfieldимя, но представилindex(довольно частоid+typeспособ) соответствовать конкретномуАтрибутыиценность. Представители по этому поводуgoogle protobuf/thriftтакже попадают в эту категорию.

(3) Нет аналитического типа

Легендарный большой BaiduinfpackРеализация достигается таким образом, отбрасывая многодостоверная информация,производительность/сжатиеЛучше, чем лучшее, но обратная совместимость требует определенной работы, и подробности неизвестны.

обменный формат тип преимущество недостаток
Xml текст легко читать Раздутый, не поддерживает бинарные типы данных
JSON текст легко читать Отброшенная информация о типе, такая как «оценка»: 100, неоднозначно, чтобы тип оценки был синтаксическим анализом int/double, двоичный тип данных не поддерживается.
Java serizable бинарный Простой в использовании Раздутый, ограниченный только полем JAVA
Thrift бинарный эффективный Нелегко читать, обратная совместимость имеет определенные ограничения по соглашению.
Google Protobuf бинарный эффективный Нелегко читать, обратная совместимость имеет определенные ограничения по соглашению.

Бережливые типы данных

  1. основной тип:  bool: логическое значение   byte: 8-битное целое число со знаком   i16: 16-битное целое число со знаком   i32: 32-битное целое число со знаком   i64: 64-битное целое число со знаком   double: 64-битное число с плавающей запятой   string: строка в кодировке UTF-8   binary: двоичная строка
  2. Тип структуры:  struct: объект определенной структуры
  3. Тип контейнера:  list: упорядоченный список элементов   set: неупорядоченный набор неповторяющихся элементов   map: упорядоченный набор ключей/значений
  4. Тип исключения:  exception: тип исключения
  5. Тип Обслуживания:  service: класс, соответствующий конкретной службе

Экономичный протокол сериализации

Thriftпозволяет пользователям выбиратьклиентиСервермеждутранспортный протокол связикатегория, вПротокол передачиВ целом делится натекст(textбинарный(binary) Протокол передачи. заэкономить пропускную способность,Повышение эффективности передачи, обычно используетсябинарныйТип транспортного протокола является большинством, а иногда и основанным натип текстаСоглашение, которое должно быть основано на реальных потребностях в проекте/продукте. Общие протоколы следующие:

  • TBinaryПротокол:бинарныйФормат кодирования для передачи данных
  • Компактный протокол:высокая эффективностьиз,плотныйизбинарныйФормат кодирования для передачи данных
  • TJSONПротокол: использоватьJSONтекстпротокол кодирования данных для передачи данных
  • TSimpleJSONProtocol: предоставляет толькоJSONпросто пишисоглашение, применимое черезРазбор скриптового языка

Тесты сериализации для Thrift

а) Сначала напишите простуюthriftдокументpair.thrift:

struct Pair {
    1: required string key
    2: required string value
}

отмечено здесьrequiredПоля , которые должны быть правильно назначены при использовании, иначе среда выполнения выдастTProtocolExceptionаномальный. по умолчанию и указан какoptional, поле не проверяется во время выполнения.

(б) Скомпилируйте и сгенерируйтеjavaИсходный код:

thrift -gen java pair.thrift

(c) Напишите тестовый код сериализации и десериализации:

  • Тест сериализации, будетPairобъект записывается в файл
private static void writeData() throws IOException, TException {
    Pair pair = new Pair();
    pair.setKey("key1").setValue("value1");
    FileOutputStream fos = new FileOutputStream(new File("pair.txt"));
    pair.write(new TBinaryProtocol(new TIOStreamTransport(fos)));
    fos.close();
}
  • Тест десериализации, синтаксический анализ сгенерирован из файлаPairобъект
private static void readData() throws TException, IOException {
    Pair pair = new Pair();
    FileInputStream fis = new FileInputStream(new File("pair.txt"));
    pair.read(new TBinaryProtocol(new TIOStreamTransport(fis)));
    System.out.println("key => " + pair.getKey());
    System.out.println("value => " + pair.getValue());
    fis.close();
}

(d) Наблюдайте за текущими результатами, нормальный вывод указывает, чтоСериализацияидесериализоватьПроцесс завершается нормально.

Исходный код протокола Thrift

(1) анализ writeData()

Выезд первымthriftизмеханизм сериализации, то есть реализация записи данных, которая принята здесьбинарный протоколTBinaryProtocol, точка входа естьpair.write(TProtocol):

Проверятьscheme()метод, принять решение о принятииПлан кортежа(TupleScheme)все ещеСтандартный план(StandardScheme) для сериализации по умолчанию используетсяСтандартный планStandardScheme.

Стандартный план(StandardScheme) подwrite()метод:

Вот несколько шагов сделано:

(а) СогласноThrift IDLопределено в файлеrequiredПоле проверяет правильность назначения поля.

public void validate() throws org.apache.thrift.TException {
  // check for required fields
  if (key == null) {
    throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not present! Struct: " + toString());
  }
  if (value == null) {
    throw new org.apache.thrift.protocol.TProtocolException("Required field 'value' was not present! Struct: " + toString());
  }
}

б) пройтиwriteStructBegin()записыватьнаписать структуруизначальный тег.

public void writeStructBegin(TStruct struct) {}

(c) Пишите один за другимPairразличные поля объекта, в том числе поляначальный тег поля,значение поляиконечный тег поля.

if (struct.key != null) {
  oprot.writeFieldBegin(KEY_FIELD_DESC);
  oprot.writeString(struct.key);
  oprot.writeFieldEnd();
}
// 省略...

(1) Первыйначальный тег поля,включаютtypeиfield-id.typeполеИдентификационный номер типа данных,field-idдаThrift IDLОпределенныйПорядок полей, сказатьkey1,valueэто 2.

public void writeFieldBegin(TField field) throws TException {
  writeByte(field.type);
  writeI16(field.id);
}

Thriftпри условииTType, для разныхтип данных(type) предоставляет уникальный идентификаторtypeID.

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;  // Map
    public static final byte SET    = 14;  // 集合
    public static final byte LIST   = 15;  // 列表
    public static final byte ENUM   = 16;  // 枚举
}

(2) Тогда напишитезначение поля, в зависимости от типа данных поля, его можно обобщить в следующую реализацию:writeByte(),writeBool(),writeI32(),writeI64(),writeDouble(),writeString()иwriteBinary()метод.

TBinaryProtocolчерез длину8изbyteкеш массива байтовнаписатьиличитатьвременные байтовые данные.

private final byte[] inoutTemp = new byte[8];

**Здравый смысл 1: **Введение в шестнадцатеричный формат. Данные, начинающиеся с 0x, представляют собой шестнадцатеричный формат, а 0xff заменяется на 255 в десятичном формате. В шестнадцатеричном формате пять букв A, B, C, D, E и F представляют собой 10, 11, 12, 13, 14 и 15 соответственно.

16базаИзменятьдесятичный: f означает 15. Вес n-го бита равен n-й степени числа 16, справа налево, начиная с бита 0: 0xff = 1516^1 + 1516^0 = 255 16базаИзменятьбинарныйизменить сновадесятичный: 0xff = 1111 1111 = 2^8 - 1 = 255

**Здравый смысл 2:** Использование побитовых операторов. >> Представляет символ сдвига вправо, например: int i=15; результат i>>2 равен 3, и сдвинутая часть будет отброшена. А >.

Преобразовать вбинарныйФорма 0000 1111(15), сдвинутая вправо на 2 бита, будет 0000 0011(3), а результат 0001 1010(18), сдвинутый на 3 бита, будет 0000 0011(3).

  • writeByte(): написатьодин байтданные.
public void writeByte(byte b) throws TException {
  inoutTemp[0] = b;
  trans_.write(inoutTemp, 0, 1);
}
  • writeBool(): написатьЛогическое значениеданные.
public void writeBool(boolean b) throws TException {
  writeByte(b ? (byte)1 : (byte)0);
}
  • writeI16(): написатькороткое целоеshortтип данных.
public void writeI16(short i16) throws TException {
  inoutTemp[0] = (byte)(0xff & (i16 >> 8));
  inoutTemp[1] = (byte)(0xff & (i16));
  trans_.write(inoutTemp, 0, 2);
}
  • writeI32(): написатьЦелое числоintтип данных.
public void writeI32(int i32) throws TException {
  inoutTemp[0] = (byte)(0xff & (i32 >> 24));
  inoutTemp[1] = (byte)(0xff & (i32 >> 16));
  inoutTemp[2] = (byte)(0xff & (i32 >> 8));
  inoutTemp[3] = (byte)(0xff & (i32));
  trans_.write(inoutTemp, 0, 4);
}
  • writeI64(): написатьдлинное целоеlongтип данных.
public void writeI64(long i64) throws TException {
  inoutTemp[0] = (byte)(0xff & (i64 >> 56));
  inoutTemp[1] = (byte)(0xff & (i64 >> 48));
  inoutTemp[2] = (byte)(0xff & (i64 >> 40));
  inoutTemp[3] = (byte)(0xff & (i64 >> 32));
  inoutTemp[4] = (byte)(0xff & (i64 >> 24));
  inoutTemp[5] = (byte)(0xff & (i64 >> 16));
  inoutTemp[6] = (byte)(0xff & (i64 >> 8));
  inoutTemp[7] = (byte)(0xff & (i64));
  trans_.write(inoutTemp, 0, 8);
}
  • writeDouble(): написатьдвойной поплавокdoubleтип данных.
public void writeDouble(double dub) throws TException {
  writeI64(Double.doubleToLongBits(dub));
}
  • writeString(): написатьТип строки, сначала напиши сюдадлина строки, затем написатьсодержимое строки.
public void writeString(String str) throws TException {
  try {
    byte[] dat = str.getBytes("UTF-8");
    writeI32(dat.length);
    trans_.write(dat, 0, dat.length);
  } catch (UnsupportedEncodingException uex) {
    throw new TException("JVM DOES NOT SUPPORT UTF-8");
  }
}
  • writeBinary: написатьдвоичный массивтип данных, где ввод данныхNIOсерединаByteBufferтип.
public void writeBinary(ByteBuffer bin) throws TException {
  int length = bin.limit() - bin.position();
  writeI32(length);
  trans_.write(bin.array(), bin.position() + bin.arrayOffset(), length);
}

(3) После того, как каждое поле написано, его необходимо записатьконечный тег поля.

public void writeFieldEnd() {}

(d).Когда все поля заполнены, необходимо записатьмаркер остановки поля.

public void writeFieldStop() throws TException {
  writeByte(TType.STOP);
}

(e).После завершения записи всех данныхwriteStructEnd()записыватьнаписать структуруиззнак завершения.

public void writeStructEnd() {}

(2) анализ readData()

Проверятьthriftизмеханизм десериализации,Сейчасчтение данныхреализовано, также используябинарный протоколTBinaryProtocol, точка входа естьpair.read(TProtocol):

чтение данныхизапись данныхто же самое, также используетсяСтандартный планStandardScheme.Стандартный план(StandardScheme) подread()метод:

Вот несколько шагов для завершения:

(пропускreadStructBeginчитатьструктураизначальный тег.

iprot.readStructBegin();

(б) Циклическое чтениеструктурасерединаВсе полевые данныеприбытьPairобъект до прочтенияorg.apache.thrift.protocol.TType.STOPдо того как.iprot.readFieldBegin()начать читатьследующее поленужно прочитать передначальный тег поля.

while (true) {
  schemeField = iprot.readFieldBegin();
  if (schemeField.type == org.apache.thrift.protocol.TType.STOP) {
    break;
  }
  // 字段的读取,省略...
}

(с). СогласноThrift IDLОпределенныйfield-idПрочтите соответствующее поле и назначьте егоPairобъект и установитьPairСоответствующие поля объектастатус чтения(посылка: поле находится вIDLопределяется какrequired).

switch (schemeField.id) {
  case 1: // KEY
    if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
      struct.key = iprot.readString();
      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);
}

ополе чтениязначение в соответствии с полемтип данныхТакже делится на следующие реализации:readByte(),readBool(),readI32(),readI64(),readDouble(),readString()иreadBinary()метод.

  • readByte(): читатьодин байтданные.
public byte readByte() throws TException {
  if (trans_.getBytesRemainingInBuffer() >= 1) {
    byte b = trans_.getBuffer()[trans_.getBufferPosition()];
    trans_.consumeBuffer(1);
    return b;
  }
  readAll(inoutTemp, 0, 1);
  return inoutTemp[0];
}
  • readBool(): читатьЛогическое значениеданные.
public boolean readBool() throws TException {
  return (readByte() == 1);
}
  • readI16(): читатькороткое целоеshortтип данных.
public short readI16() throws TException {
  byte[] buf = inoutTemp;
  int off = 0;

  if (trans_.getBytesRemainingInBuffer() >= 2) {
    buf = trans_.getBuffer();
    off = trans_.getBufferPosition();
    trans_.consumeBuffer(2);
  } else {
    readAll(inoutTemp, 0, 2);
  }

  return (short) (((buf[off] & 0xff) << 8) |
                 ((buf[off+1] & 0xff)));
}
  • readI32(): читатьЦелое числоintтип данных.
public int readI32() throws TException {
  byte[] buf = inoutTemp;
  int off = 0;

  if (trans_.getBytesRemainingInBuffer() >= 4) {
    buf = trans_.getBuffer();
    off = trans_.getBufferPosition();
    trans_.consumeBuffer(4);
  } else {
    readAll(inoutTemp, 0, 4);
  }
  return ((buf[off] & 0xff) << 24) |
         ((buf[off+1] & 0xff) << 16) |
         ((buf[off+2] & 0xff) <<  8) |
         ((buf[off+3] & 0xff));
}
  • readI64(): читатьдлинное целоеlongтип данных.
public long readI64() throws TException {
  byte[] buf = inoutTemp;
  int off = 0;

  if (trans_.getBytesRemainingInBuffer() >= 8) {
    buf = trans_.getBuffer();
    off = trans_.getBufferPosition();
    trans_.consumeBuffer(8);
  } else {
    readAll(inoutTemp, 0, 8);
  }

  return ((long)(buf[off]   & 0xff) << 56) |
         ((long)(buf[off+1] & 0xff) << 48) |
         ((long)(buf[off+2] & 0xff) << 40) |
         ((long)(buf[off+3] & 0xff) << 32) |
         ((long)(buf[off+4] & 0xff) << 24) |
         ((long)(buf[off+5] & 0xff) << 16) |
         ((long)(buf[off+6] & 0xff) <<  8) |
         ((long)(buf[off+7] & 0xff));
}
  • readDouble(): читатьс плавающей запятой двойной точностиdoubleтип данных.
public double readDouble() throws TException {
  return Double.longBitsToDouble(readI64());
}
  • readString(): читатьТип строкиданные, сначала прочитайте и проверьте4байтдлина строки, затем проверьтеNIOбуферЕсть ли байты соответствующей длины вне потребляется. Если да, то прямо избуферчитать из, иначе изканал передачичитать данные в.
public String readString() throws TException {
  int size = readI32();
  checkStringReadLength(size);

  if (trans_.getBytesRemainingInBuffer() >= size) {
    try {
      String s = new String(trans_.getBuffer(), trans_.getBufferPosition(), size, "UTF-8");
      trans_.consumeBuffer(size);
      return s;
    } catch (UnsupportedEncodingException e) {
      throw new TException("JVM DOES NOT SUPPORT UTF-8");
    }
  }

  return readStringBody(size);
}

если изканал передачичитать данные в , просматриватьreadStringBody()метод:

public String readStringBody(int size) throws TException {
  try {
    byte[] buf = new byte[size];
    trans_.readAll(buf, 0, size);
    return new String(buf, "UTF-8");
  } catch (UnsupportedEncodingException uex) {
    throw new TException("JVM DOES NOT SUPPORT UTF-8");
  }
}
  • readBinary(): читатьдвоичный массиввведите данные истрока прочитанааналогично, возвращаетByteBufferОбъект байтового буфера.
public ByteBuffer readBinary() throws TException {
  int size = readI32();
  checkStringReadLength(size);

  if (trans_.getBytesRemainingInBuffer() >= size) {
    ByteBuffer bb = ByteBuffer.wrap(trans_.getBuffer(), trans_.getBufferPosition(), size);
    trans_.consumeBuffer(size);
    return bb;
  }

  byte[] buf = new byte[size];
  trans_.readAll(buf, 0, size);
  return ByteBuffer.wrap(buf);
}

(d) После того, как данные каждого поля будут прочитаны, необходимо прочитать еще одинконечный тег поля.

public void readFieldEnd() {}

(e) Когда все поля прочитаны, нужно пройтиreadStructEnd()прочитать еще одинМаркер завершения строительства.

public void readStructEnd() {}

(f) После прочтения также необходимо проверитьThrift IDLопределяется какrequiredЯвляется ли поле пустым и законным.

public void validate() throws org.apache.thrift.TException {
  // check for required fields
  if (key == null) {
    throw new org.apache.thrift.protocol.TProtocolException("Required field 'key' was not present! Struct: " + toString());
  }
  if (value == null) {
    throw new org.apache.thrift.protocol.TProtocolException("Required field 'value' was not present! Struct: " + toString());
  }
}

Суммировать

Собственно, здесь дляThriftизмеханизм сериализацииимеханизм десериализацииизВыполнениеиЭффективность, Я считаю, что у вас уже есть более глубокое понимание!

Ссылки по теме

  1. Подробное объяснение серии Apache Thrift (1) — обзор и начало работы

  2. Подробное объяснение Apache Thrift Series (2) — Модель сетевых служб

  3. Подробное объяснение серии Apache Thrift (3) — механизм сериализации


Добро пожаловать в технический публичный аккаунт: Zero One Technology Stack

零壹技术栈

Эта учетная запись будет продолжать делиться сухими товарами серверных технологий, включая основы виртуальных машин, многопоточное программирование, высокопроизводительные фреймворки, асинхронное ПО, промежуточное ПО для кэширования и обмена сообщениями, распределенные и микросервисы, материалы для обучения архитектуре и расширенные учебные материалы и статьи.