Работа с Apache Thrift

1. Общ преглед

В тази статия ще открием как да разработваме междуплатформени клиент-сървърни приложения с помощта на RPC рамка, наречена Apache Thrift.

Ние ще покрием:

  • Дефиниране на типове данни и интерфейси на услуги с IDL
  • Инсталиране на библиотеката и генериране на източници за различни езици
  • Внедряване на дефинираните интерфейси в конкретен език
  • Внедряване на клиент / сървър софтуер

Ако искате да преминете направо към примери, преминете направо към раздел 5.

2. Apache Thrift

Apache Thrift първоначално е разработен от екипа за разработка на Facebook и в момента се поддържа от Apache.

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

Thrift използва специален език за описание на интерфейса (IDL), за да дефинира типове данни и интерфейси на услуги, които се съхраняват като .thrift файлове и се използват по-късно като вход от компилатора за генериране на изходния код на клиентски и сървърни софтуери, които комуникират на различни програмни езици.

За да използвате Apache Thrift във вашия проект, добавете тази зависимост на Maven:

 org.apache.thrift libthrift 0.10.0 

Можете да намерите най-новата версия в хранилището на Maven.

3. Език за описание на интерфейса

Както вече беше описано, IDL позволява дефиниране на комуникационни интерфейси на неутрален език. По-долу ще намерите поддържаните в момента типове.

3.1. Основни типове

  • bool - булева стойност (вярно или невярно)
  • байт - 8-битово подписано цяло число
  • i16 - 16-битово подписано цяло число
  • i32 - 32-битово подписано цяло число
  • i64 - 64-битово подписано цяло число
  • double - 64-битово число с плаваща запетая
  • низ - текстов низ, кодиран с помощта на UTF-8 кодиране

3.2. Специални видове

  • двоичен - последователност от некодирани байтове
  • по избор - незадължителен тип на Java 8

3.3. Структури

Икономичните структури са еквивалент на класове в OOP езици, но без наследяване. А структура има набор от силно въведените полета, всеки с уникално име като идентификатор. Полетата могат да имат различни пояснения (числови идентификатори на полета, незадължителни стойности по подразбиране и т.н.).

3.4. Контейнери

Спестовните контейнери са силно типизирани контейнери:

  • списък - подреден списък с елементи
  • набор - неподреден набор от уникални елементи
  • map - карта на строго уникални ключове за стойности

Елементите на контейнера могат да бъдат от всеки валиден тип Thrift.

3.5. Изключения

Изключенията са функционално еквивалентни на структурите , с изключение на това, че наследяват от родните изключения.

3.6. Услуги

Услугите всъщност са комуникационни интерфейси, дефинирани с помощта на Thrift видове. Те се състоят от набор от именувани функции, всяка със списък с параметри и тип на връщане.

4. Генериране на изходен код

4.1. Езикова поддръжка

Има дълъг списък с поддържани в момента езици:

  • C ++
  • ° С#
  • Отивам
  • Хаскел
  • Java
  • Javascript
  • Node.js
  • Perl
  • PHP
  • Python
  • Руби

Можете да проверите пълния списък тук.

4.2. Използване на изпълнимия файл на библиотеката

Просто изтеглете най-новата версия, изградете и инсталирайте, ако е необходимо, и използвайте следния синтаксис:

cd path/to/thrift thrift -r --gen [LANGUAGE] [FILENAME]

In the commands set above, [LANGUAGE] is one of the supported languages and [FILENAME] is a file with IDL definition.

Note the -r flag. It tells Thrift to generate code recursively once it notices includes in a given .thrift file.

4.3. Using Maven Plugin

Add the plugin in your pom.xml file:

 org.apache.thrift.tools maven-thrift-plugin 0.1.11  path/to/thrift    thrift-sources generate-sources  compile    

After that just execute the following command:

mvn clean install

Note that this plugin will not have any further maintenance anymore. Please visit this page for more information.

5. Example of a Client-Server Application

5.1. Defining Thrift File

Let's write some simple service with exceptions and structures:

namespace cpp com.baeldung.thrift.impl namespace java com.baeldung.thrift.impl exception InvalidOperationException { 1: i32 code, 2: string description } struct CrossPlatformResource { 1: i32 id, 2: string name, 3: optional string salutation } service CrossPlatformService { CrossPlatformResource get(1:i32 id) throws (1:InvalidOperationException e), void save(1:CrossPlatformResource resource) throws (1:InvalidOperationException e), list  getList() throws (1:InvalidOperationException e), bool ping() throws (1:InvalidOperationException e) }

As you can see, the syntax is pretty simple and self-explanatory. We define a set of namespaces (per implementation language), an exception type, a struct, and finally a service interface which will be shared across different components.

Then just store it as a service.thrift file.

5.2. Compiling and Generating a Code

Now it's time to run a compiler which will generate the code for us:

thrift -r -out generated --gen java /path/to/service.thrift

As you might see, we added a special flag -out to specify the output directory for generated files. If you did not get any errors, the generated directory will contain 3 files:

  • CrossPlatformResource.java
  • CrossPlatformService.java
  • InvalidOperationException.java

Let's generate a C++ version of the service by running:

thrift -r -out generated --gen cpp /path/to/service.thrift

Now we get 2 different valid implementations (Java and C++) of the same service interface.

5.3. Adding a Service Implementation

Although Thrift has done most of the work for us, we still need to write our own implementations of the CrossPlatformService. In order to do that, we just need to implement a CrossPlatformService.Iface interface:

public class CrossPlatformServiceImpl implements CrossPlatformService.Iface { @Override public CrossPlatformResource get(int id) throws InvalidOperationException, TException { return new CrossPlatformResource(); } @Override public void save(CrossPlatformResource resource) throws InvalidOperationException, TException { saveResource(); } @Override public List getList() throws InvalidOperationException, TException { return Collections.emptyList(); } @Override public boolean ping() throws InvalidOperationException, TException { return true; } }

5.4. Writing a Server

As we said, we want to build a cross-platform client-server application, so we need a server for it. The great thing about Apache Thrift is that it has its own client-server communication framework which makes communication a piece of cake:

public class CrossPlatformServiceServer { public void start() throws TTransportException { TServerTransport serverTransport = new TServerSocket(9090); server = new TSimpleServer(new TServer.Args(serverTransport) .processor(new CrossPlatformService.Processor(new CrossPlatformServiceImpl()))); System.out.print("Starting the server... "); server.serve(); System.out.println("done."); } public void stop() { if (server != null && server.isServing()) { System.out.print("Stopping the server... "); server.stop(); System.out.println("done."); } } } 

First thing is to define a transport layer with the implementation of TServerTransport interface (or abstract class, to be more precise). Since we are talking about server, we need to provide a port to listen to. Then we need to define a TServer instance and choose one of the available implementations:

  • TSimpleServer – for simple server
  • TThreadPoolServer – for multi-threaded server
  • TNonblockingServer – for non-blocking multi-threaded server

And finally, provide a processor implementation for chosen server which was already generated for us by Thrift, i.e. CrossPlatofformService.Processor class.

5.5. Writing a Client

And here is the client's implementation:

TTransport transport = new TSocket("localhost", 9090); transport.open(); TProtocol protocol = new TBinaryProtocol(transport); CrossPlatformService.Client client = new CrossPlatformService.Client(protocol); boolean result = client.ping(); transport.close();

From a client perspective, the actions are pretty similar.

First of all, define the transport and point it to our server instance, then choose the suitable protocol. The only difference is that here we initialize the client instance which was, once again, already generated by Thrift, i.e. CrossPlatformService.Client class.

Since it is based on .thrift file definitions we can directly call methods described there. In this particular example, client.ping() will make a remote call to the server which will respond with true.

6. Conclusion

In this article, we've shown you the basic concepts and steps in working with Apache Thrift, and we've shown how to create a working example which utilizes Thrift library.

Както обикновено, всички примери винаги могат да бъдат намерени в хранилището на GitHub.