Рубрики
Без рубрики

Как использовать буферы протокола Google в Python

Автор оригинала: Tim Grossmann.

Когда люди, которые говорят на разных языках и разговаривают, они пытаются использовать язык, который все в группе понимают.

Для этого все должны перевести свои мысли, которые обычно находятся на своем родном языке, на язык группы. Это «кодирование и декодирование» языка, однако, приводит к потере эффективности, скорости и точности. Та же концепция присутствует в компьютерных системах и их компонентах. Почему мы должны отправлять данные в XML, JSON, или любой другой читаемый человеком формат, если нам нет необходимости понять, о чем они говорят напрямую? Пока мы все еще можем перевести его в читаемый человеком формат, если явно необходимо. Буферы протокола являются способом кодировать данные перед транспортировкой, которые эффективно сокращаются блоки данных и, следовательно, увеличивают скорость при отправке его. Он абстрагирует данные в формат нейтральном языке и платформе.

Оглавление

  • Почему нам нужен буферы протокола?
  • Каковы буферы протоколов и как они работают?
  • Буферы протокола в Python
  • Финальные ноты

Почему протокольные буферы?

Первоначальная цель буферов протоколов было упростить работу с протоколами запроса/ответа. Перед Protobuf Google использовал другой формат, который требовал дополнительной обработки маршалинг Для отправленных сообщений.

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

Этот верхний мотивированный Google разработать интерфейс, который решает именно те проблемы.

Protobuf позволяет внести изменения в протокол, не нарушая совместимость. Кроме того, серверы могут проходить по данным и выполнять операции чтения по данным, не изменяя его контент.

Поскольку формат несколько самостоятельно описывает, Protobuf используется в качестве основы для автоматического генерации кода для сериализаторов и десериализаторов.

Еще одно интересное использование дело – это то, как Google использует его для короткого живого Удаленные процедуры вызовы (RPC) и настойчиво хранить данные в Bigtable. Благодаря своему конкретному случаю использования они встроили интерфейсы RPC в Protobuf. Это обеспечивает быстрое и простое поколение кода, которое можно использовать в качестве отправных точек для фактической реализации. (Больше на Protobuf RPC .)

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

Использование Гжип Сжатие в вашей коммуникации HTTPS может дополнительно улучшить эти метрики.

Каковы буферы протоколов и как они работают?

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

Google рекламирует его Protobuf Как этот :

Интерфейс Protobuf описывает структуру передачи данных. Структуры полезных нагрузок определяются как «сообщения» в том, что называется прото-файлы. Эти файлы всегда заканчиваются с .прото расширение. Например, основная структура ToList.proto Файл выглядит так. Мы также посмотрим на полный пример в следующем разделе.

syntax = "proto3";

// Not necessary for Python, should still be declared to avoid name collisions 
// in the Protocol Buffers namespace and non-Python languages
package protoblog;

message TodoList {
   // Elements of the todo list will be defined here
   ...
}

Эти файлы затем используются для создания классов интеграции или заглушек для языка по вашему выбору, используя генераторы кода в компиляторе ProTOC. Текущая версия PROTO3, уже поддерживает все основные языки программирования. Сообщество поддерживает еще много в сторонних реализациях с открытым исходным кодом.

Сгенерированные классы являются основными элементами буферов протокола. Они позволяют создавать элементы путем создания новых сообщений, основанные на .прото Файлы, которые затем используются для сериализации. Мы посмотрим, как это сделано с Python подробно в следующем разделе.

Независимо от языка для сериализации, сообщения сериализуются в невыписании, двоичном формате, которое довольно бесполезно без первоначального определения структуры.

Затем двоичные данные могут быть сохранены, отправлены по сети и использованы любые другие способы читаемые данные, такие как JSON или XML. После передачи или хранения байт-поток может быть десериализован и восстановлен с помощью любой Специализированный на языком, скомпилированном классе Protobuf, который мы генерируем из файла .proto. Используя Python в качестве примера, процесс может выглядеть что-то подобное:

Во-первых, мы создаем новый список Todo и заполните его некоторыми задачами. Этот список Todo затем сериализуется и отправляется через сеть, сохраненную в файле или постоянно хранится в базе данных.

Отправленный байтовый поток десериализируется с использованием метода анализа нашего скомпилированного класса. Большинство текущих архитектур и инфраструктур, особенно микросервисов, основаны на отдыхе, WebSOkets или Communications или GraphQL. Однако, когда скорость и эффективность необходимы, низкоуровневые RPC могут иметь огромное значение.

Вместо высоких накладных протоколов мы можем использовать быстрый и компактный способ перемещения данных между различными объектами в наш сервис, не тратя многочисленных ресурсов.

Но почему он еще не используется везде?

Буферы протокола немного сложнее, чем другие читаемые форматы. Это делает их сравнительно сложнее для отладки и интеграции в ваши приложения.

Времена итерации в технике также имеют тенденцию увеличиваться, поскольку обновления в данных требуют обновления проточных файлов перед использованием.

Тщательные соображения должны быть сделаны, поскольку Protobuf может быть переполненным решением во многих случаях.

Какие альтернативы у меня есть?

Несколько проектов принимают аналогичный подход к буферам протокола Google.

Flatbuffers Google и третья сторонняя реализация, называемая Cap’n Proto , более сосредоточены на удалении шага на разборе и распаковке, что необходимо для доступа к фактическим данным при использовании Protobufs. Они были явно разработаны для критических приложений для эффективности, делая их даже быстрее и более эффективными памятью, чем Protobuf. Сосредоточив внимание на возможностях RPC Protobuf (используется с GRPC), есть проекты от других крупных компаний, таких как Facebook (Apache Rebs) или Microsoft (протоколы облигаций), которые могут предложить альтернативы.

Python и протокол буферы

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

Скомпилировать .прото Файл языкового класса нашего выбора, мы используем Проток, Протокомпилятор. Если у вас нет установленного компилятора ProTOC, есть отличные гиды, как это сделать:

После того, как мы установили Protoc в нашей системе, мы можем использовать расширенный пример нашего списка списков TodDo из ранее и генерировать класс интеграции Python.

syntax = "proto3";

// Not necessary for Python but should still be declared to avoid name collisions 
// in the Protocol Buffers namespace and non-Python languages
package protoblog;

// Style guide prefers prefixing enum values instead of surrounding
// with an enclosing message
enum TaskState {
    TASK_OPEN = 0;
    TASK_IN_PROGRESS = 1;
    TASK_POST_PONED = 2;
    TASK_CLOSED = 3;
    TASK_DONE = 4;
}

message TodoList {
    int32 owner_id = 1;
    string owner_name = 2;

    message ListItems {
        TaskState state = 1;
        string task = 2;
        string due_date = 3;
    }

    repeated ListItems todos = 3;
}

Давайте возьмем более подробный взгляд на структуру .прото файл, чтобы понять это. В первой строке файла Proto определяем, используете ли мы прото2 или 3. В этом случае мы используем Proto3 Отказ

Наиболее необычными элементами проточных файлов – это цифры, назначенные каждому объекту сообщения. Те выделенные номера делают каждый атрибут уникальным и используются для идентификации назначенных полей в двоичном закодированном выходе.

Одна важная концепция для понимания того, что только значения 1-15 кодируются с одним байтом (Hex), что полезно для понимания, поэтому мы можем назначить более высокие числа для менее часто используемых объектов. Числа определяют ни один Заказ кодирования Ни позиция данного атрибута в закодированном сообщении.

Определение пакета помогает предотвратить попадания на имя столкновения. В Python пакеты определяются их каталогом. Поэтому предоставление атрибута пакета не имеет никакого влияния на сгенерированный код Python.

Обратите внимание, что это все равно будет объявлено, чтобы избежать протокола, связанных с именем, связанным с имени и для других языков, таких как Java.

Перечисления являются простыми списками возможных значений для заданной переменной. В этом случае мы определяем enum для возможных состояний каждой задачи в списке Todo. Посмотрим, как использовать их немного, когда мы смотрим на использование в Python. Как мы можем видеть в этом примере, мы также можем создавать сообщения внутри сообщений. Если мы, например, захотите иметь список TODOS, связанные с данным списком TODO, мы можем использовать повторяется Ключевое слово, которое сопоставимо с динамически размерами массивов.

Для генерации используемого интеграционного кода мы используем компилятор Proto, который компилирует файл заданного .proto в классы интеграции, специфичные языковые. В нашем случае мы используем -PYPON-OUT Аргумент для генерации кода Python-Special.

protoc -i =. dython_out =./todolist.proto.

В терминале мы вызываем компилятор протокола с тремя параметрами:

  1. : Определяет каталог, в котором мы ищем какие-либо зависимости (мы используем. Какой текущий каталог)
  2. – python_out : Определяет местоположение, которое мы хотим создать класс интеграции Python в (снова мы используем . Какой текущий каталог)
  3. Последний Без имени параметра Определяет файл .proto, который будет скомпилирован (мы используем файл ToList.proto в текущем каталоге)

Это создает новый файл Python, называемый _pb2.py. В нашем случае это TOLIST_PB2.PY. При приближении посмотрите этот файл, мы не сможем много понимать о своей структуре немедленно.

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

import todolist_pb2 as TodoList

my_list = TodoList.TodoList()
my_list.owner_id = 1234
my_list.owner_name = "Tim"

first_item = my_list.todos.add()
first_item.state = TodoList.TaskState.Value("TASK_DONE")
first_item.task = "Test ProtoBuf for Python"
first_item.due_date = "31.10.2019"

print(my_list)

Он просто создает новый список Todo и добавляет один предмет к нему. Затем мы распечатаем сам элемент перечисления TodDo и видно, что нечетная, несериализированная версия данных, которые мы только что определены в нашем скрипте.

owner_id: 1234
owner_name: "Tim"
todos {
  state: TASK_DONE
  task: "Test ProtoBuf for Python"
  due_date: "31.10.2019"
}

Каждый класс буфера протокола имеет методы для чтения и записи сообщений с использованием Протокол, специфичный буфер кодировки , это кодирует сообщения в двоичный формат. Эти два метода являются Сериализетостр () и ParsefromString () Отказ

import todolist_pb2 as TodoList

my_list = TodoList.TodoList()
my_list.owner_id = 1234

# ...

with open("./serializedFile", "wb") as fd:
    fd.write(my_list.SerializeToString())


my_list = TodoList.TodoList()
with open("./serializedFile", "rb") as fd:
    my_list.ParseFromString(fd.read())

print(my_list)

В приведенном выше примере кода мы пишем сериализованную строку байтов в файл, используя WB Флаги.

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

Если мы сериализуем это сообщение и распечатайте его в консоли, мы получаем представление байта, которое выглядит так.

b ‘\ x08 \ xd2 \ t \ x12 \ x03tim \ x1a (\ x08 \ x04 \ x12 \ x18test protobuf для python \ x1a \ N31.10.2019’

Обратите внимание на B перед цитатами. Это указывает на то, что следующая строка состоит из октетов байтов в Python.

Если мы напрямую сравним это с этим, например, XML, мы можем видеть, что воздействие протобуфной сериализации имеет на размер.


	1234
	Tim
	
		
			TASK_DONE
			Test ProtoBuf for Python
			31.10.2019
		
	

Представление JSON, невысокое, будет выглядеть так.

{
	"todoList": {
		"ownerId": "1234",
		"ownerName": "Tim",
		"todos": [
			{
				"state": "TASK_DONE",
				"task": "Test ProtoBuf for Python",
				"dueDate": "31.10.2019"
			}
		] 
	}
}

Судя по разным форматам, только по общему количеству используемых байтов, игнорируя память, необходимую для накладных расходов его форматирования, мы, конечно, можно увидеть разницу. Но в дополнение к памяти, используемой для данных, у нас также есть 12 дополнительных байтов в Protobuf для форматирования сериализованных данных. Сравнение этого XML, у нас есть 171 дополнительные байты в XML для форматирования сериализованных данных.

Без схемы нам нужен 136 дополнительных байтов в JSON для Форматирование сериализованные данные Отказ

Если мы говорим о нескольких тысячах сообщений, отправленных по сети или хранится на диске, Protobuf может иметь значение.

Тем не менее, есть поймать. Платформа Auth0.com создала широкое сравнение протобуфа и JSON. Он показывает, что при сжатии размерность разницы между двумя может быть маргинальной (только около 9%).

Если вы заинтересованы в точных номерах, пожалуйста, обратитесь к Полная статья , который дает подробный анализ нескольких факторов, таких как размер и скорость.

Интересная сторона примечания заключается в том, что каждый тип данных имеет значение по умолчанию. Если атрибуты не назначаются или изменены, они будут поддерживать значения по умолчанию. В нашем случае, если мы не изменим задача TaskState из ListItem, он имеет состояние «Task_open» по умолчанию. Значительное преимущество этого заключается в том, что не установленные значения не являются сериалами, сохраняя дополнительное пространство.

Если мы, например, измените состояние нашей задачи от task_done к task_open, он не будет сериализован.

owner_id: 1234
owner_name: "Tim"
todos {
  task: "Test ProtoBuf for Python"
  due_date: "31.10.2019"
}

b ‘\ x08 \ xd2 \ t \ x12 \ x03tim \ x1a & \ x12 \ x18test protobuf для python \ x1a \ N31.10.2019’

Финальные ноты

Как мы видели, буферы протокола довольно удобны, когда речь идет о скорости и эффективности при работе с данными. Из-за его мощной природы он может занять некоторое время, чтобы привыкнуть к системе Protobuf, даже если синтаксис для определения новых сообщений является простым.

В последнюю очередь, я хочу указать, что происходят/обсуждаются, что буферы протоколов «полезны» для регулярных приложений. Они были разработаны явно для проблем, которые имели в виду Google. Если у вас есть какие-либо вопросы или отзывы, не стесняйтесь обращаться ко мне в любые социальные сети, такие как Twitter или Email 🙂