Упрощение масштабируемой разработки облачного программного обеспечения с помощью Apache Thrift

Apache Thrift — это инфраструктура для масштабируемой кросс-языковой разработки, обеспечивающая беспрепятственное взаимодействие между компонентами в облачных средах. Эта статья представляет лежащие в основе Thrift концепции (определение интерфейса для удаленного вызова процедур с многоязыковыми связываниями), а затем демонстрирует использование Thrift в многоязыковом клиент/серверном приложении.

M. Тим Джонс, инженер-консультант, Emulex Corp.

М. Тим ДжонсМ. Тим Джонс - архитектор встроенного ПО и, кроме того, автор книг Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (выдержавшей на данный момент второе издание), AI Application Programming (второе издание) и BSD Sockets Programming from a Multilanguage Perspective. Он имеет обширный опыт разработки ПО в самых разных предметных областях - от ядер специальных ОС для геосинхронных космических аппаратов до архитектур встраиваемых систем и сетевых протоколов. Тим - инженер-консультант Emulex Corp., Лонгмонт, Колорадо.



28.03.2014

Создание приложений для облачных сред сопровождается таким же набором осложняющих факторов, как и разработка распределенных систем (межузловое взаимодействие, протоколы, представление сервисов и т.д.). Разработка может дополнительно усложниться при использовании языков параллельного программирования, каждый из которых имеет свою собственную систему типов. Это обуславливает необходимость преобразования типов между клиентами и серверами, чтобы гарантировать применение одинаковых форматов всеми объектами группы. Даже если система написана на одном языке программирования, для взаимодействия между этими объектами требуется эффективный и однозначный обмен информацией. А если языков становится два или больше, сложность системы и объем работ существенно увеличиваются.

Ключевая проблема при разработке распределенных систем состоит в обмене информацией между элементами программного обеспечения. Упрощение сервисов подобного типа позволяет программному обеспечению абстрагировать распределенные взаимодействия посредством механизма вызова процедур. Одна из первых реализаций технологии, которая сегодня носит название удаленного вызова процедур (Remote Procedure Call, RPC), была представлена компанией Xerox в 1981 году в виде протокола под названием Courier. Этот протокол предоставлял примитивы, которые позволяли приложениям, написанным на языке программирования Mesa, обмениваться информацией через сеть. Следует, однако, отметить, что эти приложения нуждались во вмешательстве человека для сериализации и десериализации вызовов. С того времени эта концепция широко применяется во многих приложениях. Другие примеры RPC-технологий: Common Object Request Broker Architecture, Google Protocol Buffers (protobufs), Apache Avro и многие другие.

В 2007 году компания Facebook выпустила новую реализацию RPC-технологии с открытым исходным кодом, которая обеспечивает практически беспрепятственную многоязыковую поддержку. На данный момент эта реализация поддерживается в рамках проекта организации Apache под названием Thrift. Технология Thrift поддерживает (на разных уровнях) большое количество языков программирования, в том числе Python, Ruby, Smalltalk, Haskell, Erlang, Java™, Node.js, PHP, Go, D, C/C++.

Происхождение Apache Thrift

Прежде чем превратиться в проект с открытым исходным кодом организации Apache, технология Thrift представляла собой библиотеку программного обеспечения и набор инструментов для генерации кода, которые использовались внутри компании Facebook. Эта технология была предназначена для обеспечения эффективного взаимодействия между языками программирования с использованием прозрачного высокопроизводительного программного моста.

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


Apache Thrift как лингва-франка

Apache Thrift реализует стек программного обеспечения, который упрощает разработку взаимодействующих многоязыковых приложений. На рис. 1 показано простое представление этого стека программного обеспечения. В нижней части этого стека находится физический интерфейс (например, сетевой интерфейс или что-то совсем простое, например, файл). Этот интерфейс влияет на расположенные выше уровни описываемого стека.

Стек программного обеспечения Thrift
Image showing the Thrift software stack

Thrift предоставляет несколько уровней транспортов и протоколов, которые определяют, каким образом данные перемещаются и в каком формате (с использованием уровня "Процессор" (Processor), который инкапсулирует входные и выходные потоки). Например, TBinaryProtocol— это эффективный двоичный коммуникационный протокол, который может использоваться транспортом TSocket для взаимодействия между конечными точками сети. В качестве альтернативного варианта можно использовать протокол TJSONProtocol и транспорт TMemoryTransport для взаимодействия в формате JSON (JavaScript Object Notation) через общую память.

На верхнем уровне находятся серверов тех типов, которые могут быть реализованы средствами Thrift. Эта конфигурация может включать однопоточный сервер для отладки (TSimpleServer), HTTP-сервер (THttpServer) для взаимодействия по URL-адресам согласно принципам REST (Representational State Transfer), сервер с несколькими процессами, который разветвляет процесс для каждого полученного запроса (TForkingServer) и т.д. Инфраструктура Thrift призвана упростить не только взаимодействие с использованием различных подходов (протоколы и транспортировщики), но и создание серверов с применением множества серверных стилей.

Кроме того, имеется система типов (не показанная на рис. 1), обеспечивающая взаимодействие между языками, которые поддерживает инфраструктура Thrift. Эта система поддерживает такие типы, как byte, short, int double, string, а также более изощренные типы, такие как контейнеры (списки, отображения) и структуры. Эта универсальная система типов обеспечивает единый базис для обмена данными.


Установка Thrift

Установку Thrift можно выполнить с использованием исходных кодов, что является относительно простым процессом, или с помощью менеджера пакетов, такого как apt или yum. Чтобы установить Thrift с использованием исходных кодов, просто загрузите файл gzipped tarball, сконфигурируйте его, а затем осуществите сборку с помощью утилиты make. После сборки Thrift вы сможете с легкостью осуществить установку с использованием целевых параметров установки, заданных для утилиты make. В зависимости от выбранного вами языка могут потребоваться дополнительные пакеты (например, в случае языка C++ клиентам и серверам требуется пакет Boost).

Если ваша система работает под управлением современной версии дистрибутива Linux®, то установить Thrift-компилятор можно с помощью менеджера пакетов. Например, в среде Ubuntu можно воспользоваться следующим выражением:

$ sudo apt-get install thrift-compiler

Для получения дополнительной информации обратитесь к ресурсу по установке.


RPC, системы типов и определения интерфейсов

При рассмотрении стека программного обеспечения, показанного на рис. 1, создается впечатление большой сложности решения. Thrift обладает некоторыми сложностями (как и любая многоязыковая RPC-схема), однако в действительности использование инфраструктуры Thrift намного проще.

Первый шаг в использовании Thrift для создания RPC-приложения состоит в формировании файла с определением интерфейса (см. рис. 2). Данный файл позволяет определять типы и сервисы, которые вы собираетесь представлять. Этот файл не зависит от языка и использует типы и определения Thrift. С помощью этого файла Thrift-компилятор генерирует исходные файлы (как для клиента, так и для сервера) на выбранном вами языке.

Использование Thrift компилятора для генерации исходных файлов
Image showing how to use the Thrift compiler for source generation

Рассмотрим простой пример, иллюстрирующий эту концепцию, а затем перейдем к работающему примеру. В общем случае Thrift-компилятор используется следующим образом:

$ thrift --gen <language> >.thrift file>

Представим себе простое приложение, реализующее сетевой арифметический сервис. Этот сервис предоставляет удаленным клиентам базовые математические сервисы через простой интерфейс прикладного программирования. Чтобы определить этот сервис, я создаю Thrift-файл, показанный в листинге 1. Этот файл предоставляет пространства имен пользователям различных языков (py соответствует языку Python, а rb соответствует языку Ruby). Затем я определяю сервис, который собираюсь представлять. В Thrift-определении я использую ключевое слово service и имя с целью представления своего сервиса (в данном случае этот сервис имеет имя MyMath). Определяйте свои сервисы независимым от языка образом. Мой сервис состоит из четырех представляемых функций, каждая из которых принимает набор входных значений и возвращает результат.

Учебный thrift-файл с определением интерфейса (proj.thrift)
# proj.thrift

namespace py demoserver
namespace rb demoserver

/* Каждый операнд является 32-разрядным целым числом и имеет имя Value */
typedef i32 Value
typedef i32 Result

/* Сервис MyMath представляет определенную математическую функцию */
service MyMath
{
  Result add( 1: Value op1, 2: Value op2 ),
  Result mul( 1: Value op1, 2: Value op2 ),
  Result min( 1: Value op1, 2: Value op2 ),
  Result max( 1: Value op1, 2: Value op2 )
}

Чтобы скомпилировать это определение интерфейса в виде кода, я указываю инфраструктуре Thrift требуемый язык программирования (в данном случае Python) следующим образом:

$ thrift --gen py proj.thrift

Результатом этого процесса является подкаталог с именем gen-py, содержащий несколько файлов, которые определяют типы, константы и код. Полное содержимое этого подкаталога со сгенерированным кодом показано ниже. В данном случае нас интересует код, сгенерированный для типов (ttypes.py), и стек программного обеспечения для сервиса (MyMath.py). Также обратите внимание на утилиту MyMath-remote, применение который я рассмотрю ниже в качестве способа тестирования моего нового сервиса.

$ ls gen-py/*
gen-py/__init__.py

gen-py/demoserver:
constants.py  __init__.py  __init__.pyc  MyMath.py  MyMath.pyc  MyMath-remote  
ttypes.py  ttypes.pyc
$

Однако для того, чтобы полностью продемонстрировать инфраструктуру Thrift и оценить ее возможности, одного языка недостаточно. Предположим, что клиент был написан на языке Ruby. Чтобы поддерживать такой Ruby-клиент, вы должны с помощью Thrift-компилятора сгенерировать кодовый набор Thrift для Ruby:

$ thrift --gen rb proj.thrift

В созданном при этом подкаталоге gen-rb вы найдете файлы для реализации на языке Ruby. Для каждого языка предоставляется собственная реализация стека программного обеспечения Thrift; как будет показано ниже, результирующие файлы различны, однако в любом случае они определяют типы, константы и код для реализации сервиса.

$ ls gen-rb/
my_math.rb  proj_constants.rb  proj_types.rb
$

Теперь, когда Thrift-задание дополнено файлом с определением интерфейса сервиса, я готов к созданию сервера, который будет использовать реализацию моего сервиса на языке Python.


Создание Thrift-сервера

Опираясь на определение сервиса, показанное в листинге 1, я реализую этот сервис с помощью Python. Как показано в листинге 2, мой Python-сервер весьма прост. Сначала я представляю Python-модули посредством добавления маршрута к коду, сгенерированному инфраструктурой Thrift, а затем импортирую необходимые модули, которые я собираюсь использовать. Обратите внимание, что эти модули образуют стек программного обеспечения, показанный на рис. 1. Я определяю производный класс свой математической реализации на основе базового класса, созданного в Thrift (demoserver.MyMath.Iface). Этот класс определяет реализацию для моих сервисов, организованных в Python-среде.

Заключительная часть листинга 2 представляет собой главную часть (main) скрипта, который осуществляет сборку компонентов Thrift-стека. Я создаю элемент "процессор" (processor) и инициализирую его с помощью моего класса MathImpl. Я также создаю такие элементы стека, как "протокол" (protocol) и "транспорт" (transport), и дополняю стек сервером TThreadedServer. Финальный вызов метода serve() экземпляра сервера обеспечивает обработку поточных запросов.

Thrift-сервер на языке Python (server.py)
#!/usr/bin/python

import sys

sys.path.append('./gen.py')

from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer

import demoserver.MyMath

class MathImpl( demoserver.MyMath.Iface ):
	def add( self, op1, op2 ):
		return op1 + op2
	def mul( self, op1, op2 ):
		return op1 * op2
	def max( self, op1, op2 ):
		return max([op1, op2])
	def min( self, op1, op2 ):
		return min([op1, op2])

if __name__ == '__main__':

	processor = demoserver.MyMath.Processor( MathImpl() )
	transport = TSocket.TServerSocket( port = 18181 )
	tbfactory = TTransport.TBufferedTransportFactory()
	pbfactory = TBinaryProtocol.TBinaryProtocolFactory()

	server = TServer.TThreadedServer( processor, transport, tbfactory, pbfactory )

	print 'Starting the Math Server...'

	server.serve();

Вспомните утилиту MyMath-remote, созданную для Python-сервиса. Инфраструктура Thrift сгенерировала этот файл, чтобы сделать возможным тестирование моего Python-сервера. Чтобы использовать это тестовое приложение, я должен сначала представить сгенерированные файлы в маршруте Python:

$ export PYTHONPATH=$PYTHONPATH:./gen-py

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

Использование входящего в комплект тестового приложения
$ ./gen-py/demoserver/MyMath-remote --help

Usage: ./gen-py/demoserver/MyMath-remote [-h host[:port]] 
		[-u url] [-f[ramed]] function [arg1 [arg2...]]

Functions:
  Result add(Value op1, Value op2)
  Result mul(Value op1, Value op2)
  Result min(Value op1, Value op2)
  Result max(Value op1, Value op2)

$ ./gen-py/demoserver/MyMath-remote -h localhost:18181 max 5 7
7
$ ./gen-py/demoserver/MyMath-remote -h localhost:18181 mul 9 8
72
$

Автоматически сгенерированное тестовое приложение – это хороший способ протестировать свой сервер еще до того, как будет готов клиент. Теперь поговорим о клиенте и перейдем к рассмотрению соответствующего Thrift-клиента, реализованного на языке Ruby.


Создание Thrift-клиента

Чтобы проверить функционирование своего сервера, я теперь создам клиент, который будет использовать инфраструктуру Thrift и язык Ruby. Этот процесс поможет проиллюстрировать различия в использовании Thrift с несколькими языками (в данном случае с языками Python и Ruby) и предоставит хорошую среду для многоязыкового тестирования.

В листинге 4 предоставлен полный исходный код простого Thrift-клиента на языке Ruby. Сначала я делаю модули Thrift видимыми, для чего я определяю маршрут загрузки для своего сгенерированного инфраструктурой Thrift кода на языке Ruby. Как и сервер на языке Python, мой клиент на языке Ruby создает свой стек программного обеспечения из сгенерированного кода в Ruby-модулях. Я создаю сокет (инициализированный с указанием узла сервера и местоположения порта) и соединяю его с транспортным уровнем. Этот сокет инициализируется в виде моего экземпляра протокола, который затем инициализируется с указанием сервисов.

После создания соединения с сервером (применением метода open к экземпляру transport) я получают возможность вызывать функции моего сервиса, которые сериализованы на нижележащем сервере. После завершения я закрываю транспорт, что приводит к высвобождению потока в сервере.

Thrift-клиент на языке Ruby (client.rb)
# Сделать видимым код, сгенерированный инфраструктурой Thrift
$:.push('./gen-rb')

require 'thrift'
require 'my_math'

begin

	# Построоение стека Thrift 
	transport = Thrift::BufferedTransport.new(Thrift::Socket.new('localhost', 18181))
	protocol = Thrift::BinaryProtocol.new(transport)
	client = Demoserver::MyMath::Client.new(protocol)

	transport.open()

	# Испытание операции add 
	result = client.add( 1, 5 )
	puts result.inspect

	# Испытание операции max
	result = client.max( 9, 7 )
	puts result.inspect

	transport.close()

end

Взаимодействие моего учебного клиента с исполняющимся сервером на языке Python порождает результаты, показанные в листинге 5. Это совсем небольшой объем кода для представленных здесь чувствительных к языку абстракций. Сгенерированный инфраструктурой Thrift код показывает используемые сервисы стека (MyMath.py, my_math.rb), которые для такого простого сервиса имеют достаточно большой объем.

Тестирование клиента на языке Ruby
$ ruby client.rb 
6
9
$

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


Защита Thrift-коммуникаций

Безопасность является ключевым требованием для производственных приложений и для приложений, обращенных в Интернет. Инфраструктура Thrift позволяет обеспечить безопасное взаимодействие через транспортный уровень с помощью класса TSSLTransportFactory. Эта реализация транспорта обертывает реализации TSocket и TServerSocket средствами SSL (Secure Socket Layer). Используя пару RSA-ключей, вы можете осуществлять безопасное и надежное взаимодействие между Thrift-клиентом и Thrift-сервером.


Что дальше

Thrift является отличным способом связывания сред с несколькими приложениями и организации взаимодействия между ними, однако это задача не для слабонервных. Недостаточная и устаревшая документация; различия в поддержке в зависимости от языка т. д. — все это может привести к глубокому разочарованию. В документации по Thrift отсутствуют примеры для многих поддерживаемых языков, что требует от разработчика тщательной проверки сгенерированного кода на предмет его работоспособности.

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

Ресурсы

Научиться

  • Оригинал статьи: Simplifying scalable cloud software development with Apache Thrift.
  • Веб-сайт Apache Thrift— основной источник информации о Thrift. Здесь можно получить последний релиз Thrift, а также ознакомиться с документацией и учебными пособиями по использованию Thrift.
  • Установка Thrift является сравнительно безболезненной процедурой. Если вы сталкиваетесь с проблемами или не уверены в готовности своей среды к использованию Thrift, проверьте ее на соответствие перечню Требования по сборке.
  • Thrift: Scalable Cross-Language Services Implementation (Thrift: реализация масштабируемых межъязыковых сервисов); Mark Slee, Aditya Agarwal, Marc Kwiatkowski (Facebook). Это пособие является превосходным введением в Thrift. Кроме того, в нем рассказывается о целях разработчиков, о некоторых интересных проектировочных компромиссах и о деталях реализации.
  • Знакомство с протоколом XMPP M. Тим Джонс (M. Tim Jones), developerWorks, сентябрь 2009 г. В статье описывается протокол XMPP и его применение при мгновенном обмене сообщениями. XMPP основан на технологии XML и использует ее при сериализации и при взаимодействии между клиентами и серверами. Технология XMPP нацелена на несколько иные потребности, чем Thrift, однако она представляет еще один пример взаимодействия между приложениями на разных языках.
  • В разделе Ресурсы для cloud-разработчиков на веб-сайте developerWorks вы можете обменяться знаниями и опытом с разработчиками приложений и сервисов, создающими свои проекты для развертывания в Cloud-среде.
  • Материалы developerWorks в Твиттере. Материалы автора данной статьи в Твиттере: M. Tim Jones.
  • Просмотрите демонстрации developerWorks, охватывающие диапазон от демонстраций для новичков по установке и настройке продуктов до демонстраций по углубленным функциональным возможностям для опытных разработчиков.

Обсудить

  • Присоединяйтесь к сообществу developerWorks . Связывайтесь с другими пользователями developerWorks и знакомьтесь с ориентированными на разработчиков форумами, блогами, группами и вики-ресурсами.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



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

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Облачные вычисления
ArticleID=967013
ArticleTitle=Упрощение масштабируемой разработки облачного программного обеспечения с помощью Apache Thrift
publish-date=03282014