Thrift for the First Experience of RPC Framework
Version description: thrfit 0.12.0
Module description:
- thrift-demo-java-api: Generating Java api with thrift
- thrift-demo-java-server: Java implements the Thrift server
- thrift-demo-java-client: Implementing Thrift Client in Java
- thrift-demo-py-api: Using thrift to generate Python api
- thrift-demo-py-server:Python Implementing Thrift Server
- thrift-demo-py-client: Python Implementing Thrift Client
1 Preface
The last article, "Dubbo of the initial experience of RPC framework", experienced Ali's open source RPC framework, the framework experience is fairly good, the industry also uses more. However, it only supports the Java language, not cross-language. Here we will experience a good performance, good evaluation, and support cross-language RPC framework thrfit. This article will use Java and Python to implement thrift server and client, and make cross-call.
2 Project preparation
2.1 thrfit installation
There are many ways to install thrift, such as brew install thrfit in mac environment or source code compilation. Also in linux environment, centos can use yum, ubantu can use apt, of course, unix environment can be installed by source code compilation. Thrfit also supports the installation of window s environment, download exe binary installation files on the official website for installation. Http://www.apache.org/dyn/close.cgi?Path=/thrift/0.12.0/thrift-0.12.0.exe. Of course, the official also suggested that we use docker to install thrift environment, the installation steps of thrfit are not detailed here.
2.2 Creating Projects
Let's first look at the overall directory structure.
learn-demo-thrift/ ├── README.md ├── pom.xml ├── thrfit-demo-java-server ├── thrift ├── thrift-demo-java-api ├── thrift-demo-java-client ├── thrift-demo-py-api ├── thrift-demo-py-client └── thrift-demo-py-server
The functions of several modules have been described at the beginning of the article. Another package is a thrift folder, which is used to store our. thrirft generation files and scripts.
2.2.1 Create a Maven project: learn-demo-thrift
Here you can create a Maven project directly using IDEA, specify groupId as learn.demo, artifactId as thrift, version as 1.0, and project name as learn-demo-thrift.
[External link picture transfer failure (img-MsgvI9c9-1566360312305) (https://raw.githubusercontent.com/shirukai/images/master/532984c696fa47013335e2d56c243f.jpg)]
2.2.2 Create folder thrift
In the project you just created, create a folder named thrift to save our. thrift generated files and scripts.
2.2.3 Create Maven sub-module: thrift-demo-java-api
Create Maven sub-module in the project, specify groupId as learn.demo, artifactId as thrift-demo-java-api, specify version as 1.0, module name as thrift-demo-java-api
2.2.4 Create Maven sub-module: thrift-demo-java-server
Create Maven sub-module in the project, specify groupId as learn.demo, artifactId as thrift-demo-java-server, specify version as 1.0, module name as: thrift-demo-java-server
2.2.5 Create Maven sub-module: thrift-demo-java-client
Create a Maven sub-module in the project, specify groupId as learn.demo, artifactId as thrift-demo-java-client, specify version as 1.0, and module name as thrift-demo-java-client.
2.2.6 Create Python sub-module: thrift-demo-py-api
Create a Python sub-module in the project, Python environment selection 2.7, module name: thrift-demo-py-api
2.2.7 Create Python sub-module: thrift-demo-py-server
Create a Python sub-module in the project, Python environment selection 2.7, module name: thrift-demo-py-server
2.2.8 Create a Python sub-module: thrift-demo-py-client
Create a Python sub-module in the project, Python environment selection 2.7, module name: thrift-demo-py-client
[External link picture transfer failure (img-8rIwP8tV-1566360312307)(https://raw.githubusercontent.com/shirukai/images/master/9cd73f82cf66b7228a942c54dfd4128e.jpg)]
The final project framework is created and completed.
3 Thrift API Generation
As mentioned above, thrirft supports cross-language. It supports cross-language because its services are generated from. thrift files. We can automatically generate code by defining a thrift service in a fixed format and then specifying a service language. Here we simply define a service and a data type, and generate API s for java and python respectively.
3.1 Definition. thrift file
Create a file named demo.thrift in the thrift directory under the project. The file consists of three parts:
- Namespace: Used to describe the generated language and package paths, such as namespace java learn.demo.thrift.api
- Strct: Used to define data struct ures corresponding to entity classes in Java
- service: Used to define services, which includes abstract methods of definition
Following the demo.thrift file, we define two namespace s, java and python, and define a complex data structure, including an id of int type and a name of string type, as well as a list of <\ strings > type. And it defines a service which contains two methods.
namespace java learn.demo.thrift.api namespace py thrift_demo.api struct DemoInfo{ 1:i32 id, 2:string name, 3:list<string> tags } service DemoService{ DemoInfo getDemoById(1:i32 id); void createDemo(1:DemoInfo demo) }
3.2 Create Generation Scripts
Once the thrift file is defined, we can use the thrift command for code generation, such as
thrift --gen java -out ../thrift-demo-java-api/src/main/java demo.thrift
gen specifies the generated language
out Specifies the Generation Path
For convenience, we directly create a shell script named gen-code.sh to generate java and python code at one time, as follows:
#!/usr/bin/env bash thrift --gen java -out ../thrift-demo-java-api/src/main/java demo.thrift thrift --gen py -out ../thrift-demo-py-api demo.thrift
3.3 Generating Code
Executing the gen-code.sh script generates code under thrift-demo-java-api and thrirft-demo-py-api. As shown in the following figure, the generated Python code
[External link picture transfer failure (img-Q1EVr1qO-1566360312307) (https://raw.githubusercontent.com/shirukai/images/master/ebd59b411c15bca946 abddfb9c2a468a.jpg)]
The generated Java code is shown in the following figure
3.3.1 Adding dependencies to thrift-demo-java-api
After generating Java code, let's open the code to see
[External link picture transfer failure (img-8hz OceGR-1566360312308) (https://raw.githubusercontent.com/shirukai/images/master/26dfbb0267856 c5a70f1d9738b8073.jpg)]
We found that the code was red, because we did not introduce thrift dependencies, so we introduced related dependencies in the pom file.
<dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId> <version>0.12.0</version> </dependency>
3.3.2 Release thrift-demo-py-api
We lack Maven dependencies above, and we also lack Python package dependencies here. We can use pip to install thrift packages.
pip install thrift
It's important to note that Maven modules can introduce dependencies directly into other modules, but Python can't be used directly in different modules. So here we wrap the generated Python code. Under the thrift-demo-py-api module, create a setup.py file for packaging and installation. The contents are as follows
# encoding: utf-8 from setuptools import setup, find_packages setup(name="thrift_demo_py_api", version="1.0", description="The api of thrift demo.", author="shirukai", author_email="shirukai@hollysys.com", url="https://shirukai.github.io", packages=find_packages(), scripts=[] )
Publish this module as a package to the environment
python setup.py install
So we can refer to it directly in other modules.
>>> from thrift_demo.api import DemoService >>>
4 Java Implements Server and Client
Above, we have generated Java code related to thrift service in thrift-demo-java-api. Here we use Java to implement both server and client, both of which are integrated with SpringBook.
5.1 Server: thrift-demo-java-server
The server mainly implements two aspects:
- Implementing abstract interfaces
- Implementing service exposure
Before that, we need to modify the module slightly, because it's a spring boot project, so here we specify the project parent as spring boot.
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent>
Then, api dependencies and spring boot dependencies are introduced.
<!-- demo api --> <dependency> <groupId>learn.demo</groupId> <artifactId>thrift-demo-java-api</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <!-- spring boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
5.1.1 Implementing abstract interfaces
When we define demo.thrift in Section 3, we define two abstract methods in service, and thrift generates an interface class for me.
public class DemoService { public interface Iface { public DemoInfo getDemoById(int id) throws org.apache.thrift.TException; public void createDemo(DemoInfo demo) throws org.apache.thrift.TException; } //...... }
So on the server side, we need to implement this interface first. Create a class named DemoServiceImpl under the learn.demo.thrift.server.service package. This class inherits the DemoService.Iface interface and implements the two methods. The contents are as follows
package learn.demo.thrift.server.service; import learn.demo.thrift.api.DemoInfo; import learn.demo.thrift.api.DemoService; import org.apache.thrift.TException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; /** * Created by shirukai on 2019-06-27 09:28 * Demo Service */ @Service public class DemoServiceImpl implements DemoService.Iface { private static final Logger log = LoggerFactory.getLogger(DemoServiceImpl.class); private static final Map<Integer, DemoInfo> demoCache = new HashMap<>(16); @Override public DemoInfo getDemoById(int id) throws TException { log.info("The client invoke method: getDemoById"); if (demoCache.containsKey(id)) { return demoCache.get(id); } return null; } @Override public void createDemo(DemoInfo demo) throws TException { log.info("The client invoke method: createDemo"); demoCache.put(demo.id, demo); } }
5.1.2 Implementing Service Exposure
Here we need to implement a thrift service exposure, expose a port, so that the client can communicate. Before that, we used the springboot unified configuration file to specify the ports that need to be exposed.
Create an application.properties configuration file under resources, specifying that the thrift service exposes a port of 7911
thrift.server.name=thrift-demo-server thrift.server.port=7911
Then, like a normal SpringBook application, create a startup class and create an Application class under learn.demo.thrift.server to start the SpringBook application.
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } //...... }
The implementation of Thrift service can be divided into several steps:
- Creating Processors
- Setting up a listening port
- Construct service parameters: specify processor, transport mode, transport protocol
- Creating services
- Start up service
Here we use the @Configuration form to inject our Thrift service into SpringBook applications. Create a static inner class ThriftServer Configuration in Application
@Configuration static class ThriftServerConfiguration { private static final Logger log = LoggerFactory.getLogger(ThriftServerConfiguration.class); @Value(("${thrift.server.port}")) private int serverPort; @Autowired private DemoService.Iface demoService; @PostConstruct public void startThriftServer() throws TTransportException { // Creating Processors TProcessor processor = new DemoService.Processor<>(demoService); // Listening port TNonblockingServerSocket socket = new TNonblockingServerSocket(serverPort); // Constructing service parameters TNonblockingServer.Args args = new TNonblockingServer.Args(socket); // Setting Processor args.processor(processor); // Setting up the transmission mode args.transportFactory(new TFastFramedTransport.Factory()); // Setting up Transport Protocol args.protocolFactory(new TBinaryProtocol.Factory()); // Creating services TServer server = new TNonblockingServer(args); log.info("The application is starting thrift server on address 0.0.0.0/0.0.0.0:{}",serverPort); // Start up service server.serve(); } }
5.2 Client: thrift-demo-java-client
The client is also integrated with SpringBook
- Implement client and inject Spring in the form of Bean
- Implement REST interface to demonstrate remote calls
Before that, we still need to modify the module slightly, because it is a spring boot project, so we specify the project parent as spring boot.
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent>
Then, api dependencies and spring boot dependencies are introduced.
<!-- demo api --> <dependency> <groupId>learn.demo</groupId> <artifactId>thrift-demo-java-api</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <!-- spring boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
5.2.1 Implement client and inject Spring in the form of Bean
We need to get the IP address and port number of the remote thrift service from the configuration file, so we need to create an application.properties file first.
thrift.server.name=thrift-demo-client thrift.server.ip=127.0.0.1 thrift.server.port=8911
Also create SpringBoot application startup class under learn.demo.thrift.client
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } // ...... }
Next is the key point of client implementation, which is divided into the following steps:
- Create Socket Connections
- Setting up the transmission mode
- Setting up Transport Protocol
- Connect the server
- Get client instance
Similarly, in the form of @Configuration, client instances are injected into Spring in the form of beans. Create ThriftClientConfiguration static inner class under Application class
@Configuration static class ThriftClientConfiguration { private static final Logger log = LoggerFactory.getLogger(ThriftClientConfiguration.class); @Value("${thrift.server.ip}") private String serverIp; @Value("${thrift.server.port}") private int serverPort; @Bean("demoService") public DemoService.Iface createThriftClient() throws TTransportException { // Create socket TTransport socket = new TSocket(serverIp, serverPort); // transmission mode TFramedTransport transport = new TFramedTransport(socket); // transport protocol TProtocol protocol = new TBinaryProtocol(transport); // Create a connection transport.open(); log.info("The application is creating thrift client from address {}:{} ......",serverIp,serverPort); return new DemoService.Client(protocol); } }
5.2.2 Implement REST interface to demonstrate remote calls
This is the basic content of SpringBoot Web development. We have injected Thrift client instances into Spring in the form of beans. Here we can get the instance directly through @Autowire and call its method. Create DemoController class under learn.demo.thrift.client.controller to implement two REST interfaces.
package learn.demo.thrift.client.controller; import learn.demo.thrift.client.dto.DemoInfoDTO; import learn.demo.thrift.api.DemoInfo; import learn.demo.thrift.api.DemoService; import org.apache.thrift.TException; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * Created by shirukai on 2019-06-27 10:03 * controller */ @RestController @RequestMapping(value = "/demo") public class DemoController { @Autowired private DemoService.Iface demoService; @PostMapping public String creatDemo( @RequestBody() DemoInfoDTO demoInfoDTO ) throws TException { DemoInfo demoInfo = new DemoInfo(); BeanUtils.copyProperties(demoInfoDTO, demoInfo); demoService.createDemo(demoInfo); return "success"; } @GetMapping(value = "/{id}") public DemoInfoDTO getDemo( @PathVariable("id") Integer id ) throws TException { DemoInfo demoInfo = demoService.getDemoById(id); if (demoInfo != null) { DemoInfoDTO demoInfoDTO = new DemoInfoDTO(); BeanUtils.copyProperties(demoInfo, demoInfoDTO); return demoInfoDTO; } return null; } }
5 Python Implements Server and Client
Above, we have generated Python code related to thrift service in thrift-demo-py-api, and packaged and published the module. Here we will use Python to implement the server and client.
5.1 Server: thrift-demo-py-server
Regardless of the language, the creation process of thrift's server and client is the same. There are not many articles here. The server is still implemented in two aspects:
- Implementing abstract interfaces
- Implementing service exposure
Python does not have the concept of interface, but Thrift still implements an abstract "interface" for the sake of unification, which is actually a class without concrete implementation. As follows:
class Iface(object): def getDemoById(self, id): """ Parameters: - id """ pass def createDemo(self, demo): """ Parameters: - demo """ pass
So first inherit this class and override its methods
class DemoServiceHandler(DemoService.Iface): """ //Inherit DemoService.Iface and override its method """ def getDemoById(self, id): print "The client invoke method: " + "getDemoById." if id in demoCache: return demoCache[id] else: return None def createDemo(self, demo): print "The client invoke method: " + "createDemo." demoCache[demo.id] = demo
Then there's the exposure of the server, the old routine.
- Creating Processors
- Setting up a listening port
- Initialization of transmission mode
- Initialization Transfer Protocol
- Creating services
- Start up service
Code directly
if __name__ == '__main__': handler = DemoServiceHandler() # Creating Processors processor = DemoService.Processor(handler) # Listening port transport = TSocket.TServerSocket("127.0.0.1", "8911") # Transfer Mode Factory: TBuffered Transport Factory/TFramed Transport Factory # What transmission mode does the server use, and what transmission mode does the client need to use? tfactory = TTransport.TFramedTransportFactory() # Transport Protocol Factory: TCompact Protocol/TJSONProtocol/TBinaryProtocol # What transport protocol does the server use, and what transport protocol does the client need to use? pfactory = TBinaryProtocol.TBinaryProtocolFactory() # Creating services: TSimpleServer/TForking Server/TThreadedServer/TThreadPool Server server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) print "python thrift server start" server.serve()
5.2 Client: thrift-demo-py-client
The Python client does not integrate complex services. It creates the client directly and makes remote calls.
# encoding: utf-8 """ @author : shirukai @date : 2019-06-26 21:02 thrift consumer //Official website: http://thrift.apache.org/tutorial/py#client """ from thrift.protocol import TBinaryProtocol from thrift.transport import TSocket, TTransport from thrift_demo.api import DemoService from thrift_demo.api.ttypes import DemoInfo if __name__ == '__main__': # Establishing socket transport = TSocket.TSocket('127.0.0.1', 7911) # Transport mode, consistent with the server transport = TTransport.TFramedTransport(transport) # Transport protocol, consistent with the server protocol = TBinaryProtocol.TBinaryProtocol(transport) # Create Client client = DemoService.Client(protocol) # Connect the server transport.open() # Remote Call demo = DemoInfo() demo.id = 1 demo.name = "demo1" demo.tags = ['1', '2'] client.createDemo(demo) print client.getDemoById(1)
6 Cross-validation
So far, we have completed the development of Thrift server and client in Java version and the development of service and client in Python version. The following cross-validation will be carried out to verify whether the RPC we implemented is available through the following schemes.
- Java Client-Java Server
- Java Client-Python Server
- Python Client-Python Server
- Python Client-Java Server
6.1 Java Client-Java Server
First, we start the Java server and execute the main method of the Application in thrift-demo-java-server. Exposure of Thrift service port 7911
[Img-aA3aomew-1566360312309 (https://raw.githubusercontent.com/shirukai/images/master/a85ebfeee813eee937f2551caeb9b409.jpg)]
Before starting the Java client, you need to modify the configuration file and change the service port you will need to call to 7911
thrift.server.port=7911
Then start the application
[Img-taXqchlK-1566360312309 (https://raw.githubusercontent.com/shirukai/images/master/96f111535a46793360a51c01401dfc6c.jpg)]
When the startup is complete, we can make remote calls through REST.
Create Demo
[Img-aTZS80Xl-1566360312310 (https://raw.githubusercontent.com/shirukai/images/master/76b60b4e0c5111f092cb65ecb4fa80ac.jpg)]
The server prints the log indicating that the createDemo method has been invoked
2019-06-27 15:11:53.395 INFO 43138 --- [ Thread-2] l.d.t.server.service.DemoServiceImpl : The client invoke method: createDemo
Get Demo
[Img-fkvy2aAo-1566360312310 (https://raw.githubusercontent.com/shirukai/images/master/89ae3280b5fce569d8be8475445a8e.jpg)]
The server prints the log and gets the corresponding, indicating that RPC is normal.
2019-06-27 15:13:27.957 INFO 43138 --- [ Thread-2] l.d.t.server.service.DemoServiceImpl : The client invoke method: getDemoById
6.2 Java Client-Python Server
Now we stop the above two services, change the former Java server to Python server, and start the main method in the server under the thrift-demo-py-server module. Exposure service port 8911
[Img-RjKe353F-1566360312310 (https://raw.githubusercontent.com/shirukai/images/master/2c43e404f037d4fee7b4afaa18ba7549.jpg)]
Change the port of the server that needs to be invoked in the Java client to 8911
thrift.server.port=8911
Then start the service.
PostMan is still used for REST requests.
Create Demo
[Img-trsdO0Ge-1566360312313 (https://raw.githubusercontent.com/shirukai/images/master/89ae3280b5fce569d8be8475445a8e.jpg)]
Server Print Log
The client invoke method: createDemo.
Get Demo
[External link picture transfer failure (img-L07dSG85-1566360312314) (https://raw.githubusercontent.com/shirukai/images/master/89ae3280b5fce569d8be8475445a8e.jpg)]
After verification, it shows that our RPC is normal.
6.3 Python Client-Python Server
Similarly, Python client-Python server verification is carried out.
[Img-vBTnuizb-1566360312314 (https://raw.githubusercontent.com/shirukai/images/master/795beb70286227fe21a336b0bb0b4498.gif)]
6.4 Python Client-Java Server
Similar Python Client-Java Server Verification
[Img-0 EroMOXM-1566360312314 (https://raw.githubusercontent.com/shirukai/images/master/86e7ce8a4b1d614f5168325a860226fe.gif)]
7 Summary
Thrift's first experience so far, compared with Dubbo's feeling, did not walk too many pits. Because thrift has no registry and communicates directly, it is not complicated to configure.