Use Thrift reflection to improve generic-call performance
What is Thrift Reflection ?
In short, similar to pb reflect, it does not rely on static code to add, delete, modify, and write Thrift data .
Reflection uses specific generics to describe any data at runtime. Compared with static struct, it has the characteristics of flexible addition, deletion, and modification, and does not depend on static code . Currently dynamicgo has implemented a set of reflection APIs for the thrift binary protocol, which can be roughly divided into Thrift Value/Node and Thrift DOM according to usage scenarios.
Thrift Value/Node
Used for the addition, deletion, modification, and lookup of a small number of fields in rpc data packets . For a given path (field id/field name/map key/list index), based on the data type carried by the encoding , skip unnecessary data nodes in ByteFlow , locate the required data in the start and end intervals of ByteFlow for processing. Specific usage scenarios include sensitive field erasure, data packet clipping, protocol conversion , etc. Dynamicgo provides thrift/generic. Value and thrift/generic. Node two encapsulations: the former can support lookup based on FieldName , but needs to bind dynamic type description TypeDescriptor ; the latter does not support lookup based on FieldName (but supports FieldID) , so it does not require dynamic type description .
Thrift DOM
The document object tree model is used to describe the full deserialization of Thrift data . Since the Thrift protocol itself has self-description ability, we can deserialize it into a specific tree structure for more complex container traversal , field bit shifting and other operations. Specific business scenarios include DSL mapping, data packet merging, etc. in BFF services. Dynamicgo provides thrift/generic. PathNode encapsulation, which can handle thrift data of arbitrary complexity without binding dynamic type descriptions.
Why use Thrift generics instead of Kitex Map/JSON generalization calls ?
In short: better performance .
Thrift generic requirements generally come from rpc generalization calls, http < > rpc protocol conversion and other centralized API gateways\ BFF scenarios, often with high performance requirements. However, Kitex Map/JSON generalization calls are implemented in the current map + interface mode, which inevitably brings a large number of fragmented heap memory allocation , and its performance is far worse than that of kitex rpc services in normal code generation mode. In contrast, both the efficient skip algorithm of Thrift Value and the carefully designed memory structure of Thrift DOM can effectively avoid a large number of runtime memory allocation and intermediate codec conversion. See introduction for detailed design and implementation.
For specific comparison results, please refer to the section “Test Data” below.
Usage example
The following will demonstrate how to generalize calls based on kitex-binary generalization + dynamicgo . Note unfamiliar kitex-binary generalization colleagues can refer to generic-call documents first.
See the complete example:
Server (Thrift Value + DOM)
- First, implement a type of kitex /generic/Server interface, and internally unpack the thrift-binary message into methodName, seqID, request body
- Because the use of Thrift Value here requires specific binding corresponding to the dynamic type description of the IDL , it can be loaded in the initialization stage (Note: Dynamic updates are not considered here , if necessary, you can implement an atomic update global variable)
-
Pass the request body to the business handler for processing logic. The specific operation is based on the encapsulation API of dynamicgo/thrift. Value. There are two ways to pass the path (assuming A is the Struct field name and B is the Map key):
- One-time pass:
Value.GetByPath([]Path{NewPathFieldName(A), NewPathStrKey(B)})
- Chain call:
Value.FieldByName(A).GetByStr(B)
- One-time pass:
- After processing the request, construct a response based on dynamicgo/thrift. PathNode for return
- Finally, register and start the binary generalization server
Client (Thrift Node + DOM)
- Initialize binary generalization client
- Construct business request based on dynamicgo/generic. PathNode
- Encapsulates thrift binary message, initiates binary generalization call
- ExampleClientHandler is specific business logic, here according to business needs first response body package dynamicgo/generic. Node or PathNode ( DOM ), and based on its API for business logic processing
Note that memory pooling is used for DOM access , which can greatly improve Thrift ’s deserialization performance.
Performance testing
-
Test data
-
Test code
-
Testing environment
- goos: darwin
- goarch: amd64
- cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
-
Test results
Small Data (266B)
Medium data (1766B)
Big data (150 KB )
It can be seen that as the data level continues to increase, the performance advantage of Thrift reflection over map generalization is increasing (up to more than 3 times).
Tips:
Quickly construct DOM from IDL
You can use generic.DescriptorToPathNode to quickly construct the struct DOM ( zero value)
Then traverse the DOM according to the business logic to modify specific fields
Quickly Construct a Node /Value From Sample
Assuming that the business can obtain the thrift message binary data of a normal request to the downstream service in some way, it can be used as a global constant (or timed asynchronous update), and each time NewNode ( ) + Set () updates a specific business field to achieve rapid construction of Node
Attention
- Currently dynamicgo only supports thrift-binary encoding mode
- Currently, binary generalization only supports the thrift-framed transport protocol