Привет, коллеги.
Хочу в этом топике выложить инструкцию, как быстро прикрутить Thrift, к своим поделкам.
Thrift — технология для организации межпроцессного взаимодействия между компонентами системы. Была разработана где то в недрах Facebook. Посути это кросс-языковой фреймворк для создания RPC сервисов, на бинарном протоколе. С помощью этого решения можно «подружить» компоненты написанные на разных языках C#, C++, Delphi, Erlang, Go, Java, PHP, Python, Ruby, итд. Описание сигнатур сервисов и данных осуществляется с помощью специального IDL — языка. Технология, по своей сути, похожа на COM, но без всей этой обвязки с регистрацией компонент. Так же не будем забывать, что COM это технология только для Windows, в то время как Thrift — кросплатформенна.
Вобщем решил поэкспериментировать, попробовать вынести часть нагруженной-вычислительной логики из Java в С++, в надежде что нативный С++ код будет немного производительней, и за одно опробовать Thrift RPC, в надежде что это быстрее чем REST.
Как и положено, без бубнов и граблей не обошлось!
И так, для начала надо всё это поставить:
1. Ставим поддержку для Boost, ибо всё завязано на нём
$ sudo apt-get install libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config g++ libssl-dev
2. качаем thrift tarball apache.softded.ru/thrift/0.9.0/thrift-0.9.0.tar.gz
распаковываем, запускаем configure, и затем собираем.
$ ./configure
$ make
$ sudo make install
Вроде бы всё… Можно даже попробовать сгенерировать код из учебника, который идет вместе с thrift tarball
$ thrift --gen cpp tutorial.thrift
команда thrift сгенерирует С++ обвертки, и бережно положит их в директорию gen-cpp. Тоже самое можно сделать для Java, PHP итд…
Пробуем скомпилировать и собрать нагенеренные исходники
$ g++ -Wall -I/usr/local/include/thrift *.cpp -L/usr/local/lib -lthrift -o something
Упс, получите: error: ‘uint32_t’ does not name a type
Оказывается есть небольшой таракан в библиотеках thrift связанный с uint32_t. Лечится добавлением "#include <stdint.h>" в «Thrift.h», или (что лучше всего) специальными опциями компилятора -DHAVE_NETINET_IN_H -DHAVE_INTTYPES_H
Теперь это выглядит так:
$ g++ -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -Wall -I/usr/local/include/thrift *.cpp -L/usr/local/lib -lthrift -o something
Вот и всё, появился исполнимый файл, под названием something.
Запускаем, и получаем: error while loading shared libraries: libthrift.so.0: cannot open shared object file: No such file or directory
Возможно есть какие-то элегантные методы решения этой проблемы, но я решил её в лоб, копированием thrift файлов из /usr/local/lib в /lib
Всё, пример запустился. Значит, все на местах, и всё работает.
Теперь можно писать свой RPC сервер.
Его задача, быть key-value хранилищем. Хранить длинные (в несколько сот тысяч) битовые маски. Складывать их (AND), и отдавать клиенту массив индексов, в которых получились еденицы. Да, почти тоже самое умеет Redis, но он мне не подходит.
Полный код лежит здесь: github.com/2anikulin/fast-hands.git
Описываем сигнатуры данных и сервисов, в thrift definition file:
namespace cpp fasthands
namespace java fasthands
namespace php fasthands
namespace perl fasthands
exception InvalidOperation {
1: i32 what,
2: string why
}
service FastHandsService {
i32 put(1:i32 key, 2:binary value),
binary get(1:i32 key),
list <i32> bitAnd(1:list<i32> keys) throws (1:InvalidOperation ouch)
}
И генерируем обвертки.
Реализация на C++
Этот код, создает, и запускает RPC сервер
#define PORT 9090
#define THREAD_POOL_SIZE 15
int main() {
printf("FastHands Server startedn");
shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
shared_ptr<FastHandsHandler> handler(new FastHandsHandler());
shared_ptr<TProcessor> processor(new FastHandsServiceProcessor(handler));
shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(THREAD_POOL_SIZE);
shared_ptr<PosixThreadFactory> threadFactory = shared_ptr<PosixThreadFactory>(new PosixThreadFactory());
threadManager->threadFactory(threadFactory);
threadManager->start();
TNonblockingServer server(processor, protocolFactory, PORT, threadManager);
server.serve();
printf("done.n");
return 0;
}
В классе FastHandsHandler — имплементируется весь наш, прикладной функционал
Это заголовочный файл
class FastHandsHandler : virtual public FastHandsServiceIf {
public:
FastHandsHandler();
int32_t put(const int32_t key, const std::string& value);
void get(std::string& _return, const int32_t key);
void bitAnd(std::vector<int32_t> & _return, const std::vector<int32_t> & keys);
private:
void appendBitPositions(std::vector<int32_t> & positions, unsigned char bits, int offset);
private:
std::map<int32_t, std::string> m_store;
};
Пробуем собрать, и получаем очередную ошибку: c++ undefined reference to apache::thrift::server::TNonblockingServer
Дело в том, что, в отличии от учебника, мой сервер — ассинхронный, и использует класс TNonblockingServer. Чтобы код собирался, надо добавить дополнительные библиотеки -lthriftnb -levent
т.е сборка сейчас будет выглядеть так:
g++ -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -Wall -I/usr/local/include/thrift *.cpp -L/usr/local/lib -lthrift -lthriftnb -levent -o something
Теперь все хорошо. Код скомпилирован, слинкован, на выходе файл по имени something
Генерируем обвертки для java, и пишем вот такого клиента
import fasthands.FastHandsService;
import fasthands.InvalidOperation;
import org.apache.thrift.TException;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class JavaClient {
public static void main(String [] args) {
TTransport transport = new TFramedTransport(new TSocket("localhost", 9090));
TProtocol protocol = new TBinaryProtocol(transport);
final FastHandsService.Client client = new FastHandsService.Client(protocol);
final List<Integer> filters = new ArrayList<Integer>();
try {
transport.open();
int count = 12500;
byte bt[] = new byte[count];
for (int i =0; i < count; i++) {
bt[i] = (byte)0xFF;
}
for (int i = 0; i < 50; i++) {
client.put(i, ByteBuffer.wrap(bt)) ;
filters.add(i);
}
List<Integer> list = client.bitAnd(filters);
System.out.println(list.size());
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
transport.close();
}
}
Что в итоге.
Интересная технология, и не плохой способ прикрутить транспортный функционал к голому коду на С++. Но не скажу, что это намного быстрее чем REST, бинарные данные прекрасно передаются и по http. Что касается производительности кода, вынесенного из Java в С++, то чуда не произошло, он быстрее в 1.2 — 1.4 раза, ибо сериализация + расходы на транспортный уровень.
Автор: 2ANikulin