Apache Thrift

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск
Apache Thrift
ASF-logo.svg
Тип

RPC framework

Разработчик

Apache Software Foundation

Написана на

C++

Операционная система

Кроссплатформенное программное обеспечение

Последняя версия

0.9.1 (21.08.2013)

Лицензия

Apache License 2.0

Сайт

thrift.apache.org

Thrift (англ. Бережливость, произносится как θrɪft) — язык описания интерфейсов, который используется для определения и создания служб под разные языки программирования. Является фреймворком к удаленному вызову процедур (RPC). Разработан компанией Facebook в качестве масштабируемого кросс-языкового сервиса по разработке. Сочетает в себе программный конвейер с движком генерации кода для разработки служб, в той или иной степени эффективно и легко работающих между такими языками как C#, C++, Cappuccino, Cocoa, Delphi, Erlang, Go, Haskell, Java, OCaml, Perl, PHP, Python, Ruby и Smalltalk. Проще говоря, Thrift является двоичным протоколом связи. С апреля 2007 разрабатывается как open source проект компанией Apache Software Foundation.

Архитектура[править | править исходный текст]

Thrift включает в себя готовый программный конвейер, состоящей из шести уровней, для работы с клиентской и серверной частью. Верхний уровень составляет сгенерированный код описания Thrift. Службы генерируют из него клиентский и серверный код. В отличие от встроенных типов, созданная структура данных возвращается как результат в сгенерированном коде. Уровни протокола и транспортировки являются частью runtime-библиотеки. В Thrift возможно выбрать службы и изменить протокол и транспортировку без перекомпиляции кода. Помимо клиентской части Thrift включает в себя серверную инфраструктуру для связи протокола и транспортировки в blocking, non-blocking и multi-threaded серверах. Основа уровня Ввода/Вывода по-разному реализована для различных языков.

Поддерживаемые протоколы[править | править исходный текст]

  • TBinaryProtocol — Несложный двоичный формат, простой, но не оптимизированный для экономии пространства.
  • TCompactProtocol — Более компактный двоичный формат, как правило более эффективен.
  • TDebugProtocol — Человечески понятный формат текста, помогающий в отладке.
  • TDenseProtocol — Как и в TCompactProtocol, получение мета информации из того, что было передано.
  • TJSONProtocol — Использование JSON’a для раскодировки данных.
  • TSimpleJSONProtocol — Протокол «только для записи», использующий JSON. Подходит для парсинга на скриптовых языках.

Поддерживаемые транспортировщики[править | править исходный текст]

  • TFileTransport — Этот транспортировщик записывает в файл.
  • TFramedTransport — Этот транспортировщик используется, когда применяются non-blocking сервера. Он отправляет данные во фреймах, где каждому фрейму предшествует длина информации.
  • TMemoryTransport — Использование памяти для ввода / вывода. Реализация Java использует простой встроенный ByteArrayOutputStream.
  • TSocket — Использует blocking socket ввода / вывода для транспортировки.
  • TZlibTransport — Выполняет сжатие с помощью zlib. Используется в сочетании с другим транспортом. Отсутствует в реализации Java.

Поддерживаемые сервера[править | править исходный текст]

  • TNonblockingServer — multi-threaded сервер, использующий non-blocking ввод / вывод (Java реализация использует NIO channels). На этих серверах должен использоваться TFramedTransport.
  • TSimpleServer — single-threaded, использующий std blocking ввод / вывод. Полезен для тестирования.
  • TThreadPoolServer — multi-threaded сервер, использующий std blocking ввод / вывод.


Преимущества[править | править исходный текст]

  • Кросс-языковая сериализация с более низкими накладными расходами, в отличие от таких альтернатив как SOAP, за счет использования двоичного формата.
  • Простая и чистая библиотека. Не нуждается в фреймворке для кода. Не использует XML-конфигурацию.
  • Языковые привязки ощущаются естественными. Например, Java использует ArrayList<String>. C++ использует std::vector<std::string>.
  • Формат связи уровня приложений и формат связи уровня сериализации строго разделены. Они могут быть изменены независимо друг от друга.
  • Встроенные типы сериализации включают в себя: двоичный, дружественный к HTTP и компактный двоичный.
  • Складывается, как кросс-языковой сериализованный файл.
  • Мягкие версии протокола. Thrift не требует централизованной и явный механизм, как major-version/minor-version. Слабосвязанные группы могут свободно переходить в RPC вызовы.
  • Независим от архитектуры или от нестандартного ПО. Нет несовместимых лицензий на ПО.

Сравнение с Protocol Buffers[править | править исходный текст]

Apache Thrift Protocol Buffers
Разработчик Facebook, Apache Google
Поддерживаемые языки C++, Java, JavaScript, Python, PHP, XSD, Ruby, C#, Perl, Objective C, Erlang, Smalltalk, OCaml, and Haskell C++, Java, Python (Perl, Ruby, and C# under discussion)
Исходящие форматы Binary, JSON Binary
Простые типы bool
byte
16/32/64-bit integers
double
string
byte sequence
map<t1,t2>
list<t>
set<t>
bool
32/64-bit integers
float
double
string
byte sequence
"repeated" properties act like lists
Константы Да Нет
Составной тип struct message
Исключения Да Нет
Документация Проблематично Хорошая
Лицензия Apache BSD-style
Композитные расширения типов Нет Да

Создание Thrift службы[править | править исходный текст]

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

enum PhoneType {
 HOME,
 WORK,
 MOBILE,
 OTHER
}
 
struct Phone {
 1: i32 id,
 2: string number,
 3: PhoneType type
}

Thrift сгенерирует код из этого файла описания, например, в Java. PhoneType будет простым перечислением (enum) внутри POJO для класса Phone.

Подробный пример[править | править исходный текст]

Напишем простое приложение на Apache Thrift. Мы будем определять объект User. Этот объект будет создан в PHP и располагаться в списке языка Python. Можно было бы также получить объект из Python и передать его в PHP или очистить список. Хотя этот пример очень примитивный, это даст вам некоторое представление о том, как работает Thrift Apache. Для начала мы должны определить методы и объекты, которые будут взаимодействовать с обоими приложениями. Эти определения мы поместим в файл с именем "hello.thrift". У этого файла C-подобный синтаксис, со специфическими модификаторами Thrift’a, как будет показано позже.

Thrift файл может содержать другие файлы. В нашем примере мы не будем вкладывать никакие другие файлы. Однако в официальном руководстве проекта вы можете заметить, что требуется файл "shared.thrift". Этот файл объявляет класс, от которого наследуется основной класс Calculator.

Для начала давайте зададим пространство имен.

namespace php hello

Пространства имен определяются для каждого языка — в данном примере, только объекты в файле PHP будут с префиксом «hello_».

Объект User содержит поля firstname и lastname строкового типа, user_id типа integer, active типа boolean и sex нашего собственного типа перечисления (enum). Описание такого объекта является очень простым. Для начала опишем наш собственный тип enum.

enum SexType {
  MALE = 1,
  FEMALE = 2
}

После чего опишем объект User.

struct User {
  1: string firstname,
  2: string lastname,
  3: i32 user_id = 0,
  4: SexType sex,
  5: bool active = false,
  6: optional string description
}

Как вы, наверное, уже заметили, мы пронумеровали параметры, поскольку того требует синтаксис Thrift. По умолчанию все параметры являются обязательными, но вы можете сделать их необязательными (с помощью ключевого слова optional), а также установить их значения по умолчанию. Нам так же надо объявить исключение. Оно будет вызываться, когда передается неправильной параметр (в нашем примере это может произойти, если, например, user_id будет отрицательным).

exception InvalidValueException {
  1: i32 error_code,
  2: string error_msg
}

Теперь пришло время объявить службу. Службы — это основная вещь, проводящая сериализацию объектов для их доступа в различных программах. Объявим нашу простую службу с помощью ключевого слова service

service UserManager {
  void ping(),
  i32 add_user(1:User u) throws (1: InvalidValueException e),
  User get_user(1:i32 uid) throws (1: InvalidValueException e),
  oneway void clear_list()
}

При объявлении метода правила нумерации параметров сохраняются. Как вы можете видеть, объявление метода очень похоже на C.

Небольшое описание методов[править | править исходный текст]

  • ping — простой ping-pong метод. Если мы отправляем сообщение "are you there?" и получаем ответ "yes, I am here", то, конечно же, все идет правильно.
  • add_user — отправляем объект user (это объект, поэтому он должен быть создан в PHP-приложении). В ответ мы получаем user_id. Если что-то не так, нам возвращается ошибка.
  • get_user — то же, что и выше, но здесь мы отправляем user_id и получаем объект user
  • clear_list — эта функция объявлена ​​как односторонняя (oneway) — это значит, запрос будет отправлен, но наше PHP-приложение не будет ждать результата (так как там может не быть ни одного). Очевидно, что значения такой функции должны считаться недействительными.

Генерируем файлы[править | править исходный текст]

Теперь мы можем сгенерировать PHP и Python файлы, которые будут использоваться на клиентской стороне и на сервере, соответственно. Это довольно просто:

thrift -r --gen php hello.thrift
thrift -r --gen py hello.thrift

В директориях gen-php и gen-py мы получили PHP и Python файлы, соответственно. Теперь давайте создадим сервер. Создаем файл, назовем его, например, python_server.py. Не забудьте сделать его исполняемым, с помощью команды:

chmod +x ./python_server.py

В этот файл мы запихнем все необходимые библиотеки, определим класс для обработки запросов с такими же именами методов, как и определенный в thrift-файлах. Так как код говорит сам за себя, приведем его ниже.

#!/usr/bin/env python
 
import sys
sys.path.append('./gen-py')
 
from hello import UserManager
from hello.ttypes import *
 
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
 
users = []
 
class UserManagerHandler:
	def __init__(self):
		pass
		#self.log = {}
 
	def ping(self):
		print 'ping()'
 
	def add_user(self, user):
		if user.firstname == None:
			raise InvalidValueException(1,'no firstname exception')
		if user.lastname == None:
			raise InvalidValueException(2, 'no lastname exception')
		if user.user_id <= 0:
			raise InvalidValueException(3, 'wrong user_id')
		if user.sex != SexType.MALE and user.sex != SexType.FEMALE:
			raise InvalidValueException(4, 'wrong sex id')
		print 'Processing user '+user.firstname+' '+user.lastname
		users.append(user)
		print users
		return True
 
	def get_user(self, user_id):
		if user_id < 0:
			raise InvalidValueException(5, 'wrong id')
		return users[user_id]
 
	def clear_list(self):
		print 'Clearing list'
		print users
		del users [:]
		print users
 
handler = UserManagerHandler()
processor = UserManager.Processor(handler)
transport = TSocket.TServerSocket(port=9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
 
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
 
# You could do one of these for a multithreaded server
#server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
#server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
 
print 'Starting the server...'
server.serve()
print 'done.'

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

Следующую строку обязательно надо принять во внимание:

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

Это путь к директории gen-py, относительно того места, откуда будет запущен скрипт (по сути, лучшим решением является указать здесь абсолютный путь).
Вы можете запустить ваш скрипт командой:

./python_server.py


Однако, вы можете столкнуться со следующей ошибкой: ImportError: No module named Thrift
Чтобы это исправить, вам надо перейти в директорию lib/py и запустить команду установки thrift модуля в вашу python библиотеку:

sudo python setup.py install


Перед тем, как создать PHP скрипт подготовим структуру файлов. Для начала выберем директорию, которая доступна из вашего Apache или любого другого http-сервера. Затем создадим директорию src и поместим туда все файлы и директории из директории lib/php/src в пакете Thrift. Создадим так же директорию src/packages и поместим туда директорию hello из директории gen-php (созданную Thrift’ом). В корневую директорию поместите ваш PHP-файл (то есть «hello.php»). В этот скрипт необходимо вставить Thrift-библиотеки, ваш автоматически сгенерированный файл с классом UserManager, установить соединения с сервером и выполнить обмен объектами.

<?php 
$GLOBALS['THRIFT_ROOT'] = 'src';
 
require_once $GLOBALS['THRIFT_ROOT'].'/Thrift.php';
require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/THttpClient.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php';
 
require_once $GLOBALS['THRIFT_ROOT'].'/packages/hello/UserManager.php';
try {
 
	$socket = new TSocket('localhost', 9090);
	$transport = new TBufferedTransport($socket, 1024, 1024);
	$protocol = new TBinaryProtocol($transport);
	$client = new UserManagerClient($protocol);
 
	$transport->open();
	$client->ping();
	$u = new hello_User();
	$u->user_id = 1;
	$u->firstname = 'John';
	$u->lastname = 'Smith';
	$u->sex = hello_SexType::MALE;
	if ($client->add_user($u))
	{
		echo 'user added succesfully</br>';
	}
 
	var_dump($client->get_user(0));
 
	$client->clear_list();
 
 
 
	$u2 = new hello_User();
	$client->add_user($u2);
 
} catch (hello_InvalidValueException $e) {
	echo $e->error_msg.'<br/>';
}
 
?>


Теперь запустите свой сервер (если, конечно, вы его еще не запустили) и откройте ваш PHP-файл в браузере. Вы должны получить следующий результат:

user added succesfully
object(hello_User)[7]
  public 'firstname' => string 'John' (length=3)
  public 'lastname' => string 'Smith' (length=8)
  public 'user_id' => int 1
  public 'active' => boolean true
  public 'sex' => int 1
  public 'description' => null
no firstname exception

Результат var_dump'а может отличаться, если у вас не установлен xdebug

Когда вы будете анализировать содержимое сценария PHP, вы заметите, что первый пользователь был добавлен успешно, а на второй попытке с различными данными будет поймано исключение (exception). В консоли это будет выглядеть так:

home-debian:~/www/thrift-test/server# ./python_server.py
Starting the server...
ping()
Processing user John Smith
[User(user_id=1, description=None, firstname='John', lastname='Smith', sex=1, active=True)]
Clearing list
[User(user_id=1, description=None, firstname='John', lastname='Smith', sex=1, active=True)]
[]


Вот собственно и всё.

Сайты, использующие Thrift[править | править исходный текст]

Facebook — социальная сеть и первый разработчик Thrift.
facebook.com
last.fm — веб-анализатор музыки.
last.fm
reCaptcha — самая популярная каптча.
recaptcha.com
RapLeaf
rapleaf.com
Evernote — онлайн-сервис и ряд приложений для различных платформ, предназначенные для сохранения, синхронизации и поиска заметок.
evernote.com
OpenX
openx.com
Mendeley — бесплатный онлайн-сервис для управления библиографической информацией, позволяющая хранить и просматривать исследовательские труды в формате PDF.
mendeley.com
ONEsite
onesite.com
api.2gis.ru
api.2gis.ru

Ссылки[править | править исходный текст]