Apache Thrift RPC Server. Дружим C++ и Java

в 6:05, , рубрики: c++, java, linux, thrift, Программирование, метки: , , ,

Привет, коллеги.
Хочу в этом топике выложить инструкцию, как быстро прикрутить 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

Источник

Поделиться

* - обязательные к заполнению поля