This article is reproduced from here is the original
"In-depth" we mainly focus on RPC's functional objectives and implementation considerations to expand, a basic RPC framework should provide what functionality, what requirements and how to implement it?
RPC feature Target
The main function of RPC is to make it easier to build distributed computing without losing the semantic simplicity of local calls while providing powerful remote invocation capabilities. To achieve this goal, the RPC framework provides a transparent call mechanism that allows the user to explicitly distinguish between local calls and remote calls, in the previous article "Shallow out" gives a time structure, based on the structure of the stub is implemented, below we will refine the implementation of the stub structure.
RPC Call classification
RPC calls are divided into the following two types:
Synchronous call > Client waits for call execution to complete and return results
Asynchronous calls > client calls do not wait for the execution result to return, but can still get the return result through callback notification, and so on, if the client does not relate to call return results, it becomes one-way asynchronous call, one-way call does not return results.
RPC Fabric Disassembly
"Shallow out" gives a coarse-grained RPC implementation concept structure, where we further refine which components it is composed of, as shown in:
The RPC server exports the remote interface method through Rpcserver, and the client introduces the remote interface method through Rpcclient. The client direction calls the local method to invoke the remote method, the RPC framework provides the proxy implementation of the interface, and the actual invocation is delegated to RpcProxy. The proxy wrapper calls the credit information to rpcinvoker the call to the actual execution, rpcinvoker the client through the connector Rpcconnector to maintain the server-side channel Rpcchannel, and uses RpcProtocol to execute the Protocol encoding ( Encode) and sends the encoded message through the channel to the service party.
RPC Service side Rpcacceptor to accept the client's call request, also use RpcProtocol to perform protocol decoding (decode). The decoded message is passed to Rpcprocessor to control the processing of the call, and finally to the Rpcinvoker in the delegate invocation to actually execute and return the result of the call.
RPC Component Responsibilities
Above we further disassemble the RPC implementation structure of the Brother component part, below we detail the responsibilities of each part:
-
- Rpcserver > responsible for exporting interfaces
-
- Rpcclient > responsible for importing interfaces
-
- RpcProxy > Remote Interface proxy implementation
-
- Rpcinvoker > Client implementations: Responsible for encoding the call information and sending the call information and waiting for the call to return. > Service-Side implementation: Responsible for invoking the server-side interface implementation and return the result of the call.
-
- RpcProtocol > responsible for protocol coding and decoding
-
- Rpcconnector > is responsible for maintaining the customer and service side of the understanding of the channel and send data to the service party
-
- Rpcacceptor > is responsible for receiving client's request and returning processing result
-
- Rpcprocessor > is responsible for managing the call process in the service side, including the thread pool and the time-out of the call.
-
- Rpcchannel > Data transmission channels
RPC Implementation Analysis
After further dismantling the components and dividing the responsibilities, here is an example of implementing the RPC framework conceptual model on the Java platform, with a detailed analysis of the factors to consider in the implementation:
Export Remote Interface
Exporting the remote interface means that only the exported interface is available for remote invocation, and the non-exported interface is not available, and the Code snippet for exporting the interface in Java might look like this:
DemoServer demo = new ...; RpcSerber server = new ...; server.export(Demoserver.class, demo, options)
We can export the entire interface, or we can export only some of the methods in the interface at a finer granularity, such as:
//只导出Demoserver中签名为hi(string s) 的方法 server.export(DemoServer.class, demo, "hi", new Class<?>[] {String.class},options)
In Java there is a more special call is polymorphic, that is, an interface may have multiple implementations, then the remote invocation of which is called exactly? The semantics of this local invocation are implicitly implemented by the reference polymorphism provided by the JVM, and for RPC, cross-process calls cannot be implicitly implemented. If the previous Demoservice interface has 2 implementations, you need to specifically tag different implementations when exporting the interface, such as:
DemoserviceDemo=New...;demoservice demo2 = new rpcserver server = new server. (demoservice. Classdemooptions Server. ( "Demo2" demoservice.< Span class= "NA" >classdemo2options
The above Demo2 is another implementation, we mark it as Demo2 to export, then the remote call also need to pass the token to invoke the correct implementation class, which solves the semantics of the polymorphic call.
Import the remote interface with the client Agent
The import is relative to the export remote interface, in order for the client code to be able to invoke a method or procedure definition that must obtain the remote interface. At present, most of the cross-language platform RPC framework uses code generator to generate stub code based on the IDL definition, in this way the actual import process is done through the coding generator at compile time. Some of the cross-lingual platform RPC frameworks I've used, such as Corbar, WebService, ICE, and Thrift, are all of these ways.
The way code is generated is an inevitable choice for cross-language platform RPC frameworks, and RPC for the same language platform can be implemented through shared interface definitions. The code snippet for importing an interface in Java might look like this:
RpcClient client = new ...; DemoServer demo = client.refer(DemoServer.class); demo.hi("how are you");
Import is a keyword in Java, so we use refer to express the meaning of the import interface in the code snippet. The import method here is essentially a code generation technique, but it is generated at run time, which is more concise than the code generation at the static compile time. Java provides at least two techniques to provide dynamic code generation, one is the JDK dynamic agent, and the other is bytecode generation. Dynamic proxies are easier to use than bytecode generation, but the dynamic proxy approach is less performance-generated than direct bytecode generation, and bytecode generation is much worse in code readability. The tradeoff between the two is that it is more important for individuals to sacrifice some performance to gain code readability and maintainability.
Protocol encoding and decoding
The client agent needs to encode the invocation information before initiating the call, which takes into account what information needs to be encoded and in what format to be transmitted to the server to allow the server to complete the call. For efficiency reasons, the less information you encode, the better (less data is transmitted), and the simpler the coding rule is, the better (and the more efficient). Let's start by looking at what we need to code:
Call encoding
- Interface method
Include interface name, method name
- Method parameters
Include parameter type, parameter value
- Call Properties
Includes calling property information, such as calling attachment implicit arguments, calling time-outs, and so on
return encoding
- return results
The return value defined in the interface method
- Return code
Exception return code
- Return exception information
Calling exception information
In addition to these necessary invocation information, we may need some meta-information to facilitate program decoding and possible future extensions. In this way our code message is divided into two parts, part of the meta-information, and the other part is the necessary information to invoke. If you design an RPC protocol message, the meta-information is placed in the protocol message header, and the necessary information is placed in the protocol message body. The following is a conceptual format for RPC protocol message design:
Message header
- Magic: Protocol magic number, for decoding design
- Header size: Protocol header length, for extended design
- Version: Protocol versions, for extended use
- ST: type of message body serialization
- HB: Heartbeat information marker for long-connected transmission layer heartbeat design
- OW: One-way message flag
- RP: The corresponding message token. Pail bit default is request message
- Status code: Response message State Code
- Reserved: reserved for byte alignment
- Message ID: Msg ID
- Body size: Message body length
Message body
The following formats are commonly used for serialization encoding:
- XML: such as WebService (SOAP)
- JSON: such as Json-rpc
- Binary: such as Thrift,hession,kryo, etc.
Format determined after the codec is simple, because the length of the head must be so we are more concerned about the message body serialization mode. Serialization we care about three areas:
- The more efficient the serialization and deserialization, the faster the better.
- The byte length after serialization, the smaller the better.
- serialization and deserialization compatibility, interface parameter object if the field is added, whether it is compatible.
Above these three points is sometimes the fish and bear paw can not have, which involves the specific serialization library implementation details, not in this article further analysis.
Transfer Service
After the protocol is encoded, it is natural to transfer the encoded RPC request message to the service party after the service party executes and returns the result message or acknowledgment message to the client. The application scenario of RPC is essentially a reliable request-reply message flow, similar to HTTP. Therefore, the choice of long-connection TCP protocol is more efficient, unlike HTTP is at the protocol level we define a unique ID for each message, so it is easier to reuse the connection.
With long connections, the first question is how many root connections are needed between the client and server? In fact, single-connection and multi-connection in the use of no difference, for the low data transmission of the application type, a single connection is basically enough. The biggest difference between single-and multi-connection is that each connection has its own private send and receive buffers, so a large amount of data can be distributed over different connection buffers for better throughput efficiency. So, if your data transfer volume is not enough to keep a single-connected buffer saturated, then using multiple connections does not create any noticeable elevation, but increases the overhead of connection management.
The connection is initiated and maintained by the client side. If the client and server are directly connected, the connection is generally uninterrupted (except for physical link failures, of course). If the client and server connections are connected through some load relay devices, it is possible that these intermediate devices will be interrupted when the connection is inactive for a period of time. In order to maintain connectivity it is necessary to periodically send heartbeat data for each connection to maintain the connection uninterrupted. The heartbeat message is an internal message used by the RPC framework library, and there is a dedicated heartbeat bit in the previous protocol header structure that is used to mark the heartbeat message, which is transparent to the business application.
Execute call
What the client stub does is simply encode the message and transfer it to the service party, and the actual invocation process takes place on the service side. Server stub from the previous structure of the disassembly we subdivide the rpcprocessor and rpcinvoker two components, one is responsible for controlling the call process, one responsible for the real call. Here we also take the implementation of these two components in Java as an example to analyze what they need to do?
Dynamic interface calls to implement code in Java are now generally invoked through reflection. In addition to the native JDK's own reflection, some third party libraries provide better-performing reflection calls, so rpcinvoker is the implementation detail that encapsulates the reflection invocation.
What are the factors that need to be considered in the control of the calling process, and what rpcprocessor need to provide to invoke the control service? Here are some ideas to enlighten:
Each request should be executed as soon as possible, so we cannot create threads for each request to execute and need to provide a thread pool service.
When we export multiple remote interfaces, how to prevent a single interface call from occupying all of the thread resources and throwing other interfaces to execute blocking.
When an interface executes slowly, and the client side has timed out the wait, the server-side thread continues to execute at this point, which makes no sense.
RPC Exception Handling
No matter how RPC tries to disguise remote calls as local calls, they are still very different, and there are some exceptions that are never encountered when called locally. Before we say exception handling, let's compare some of the differences between local calls and RPC calls:
- The local call is bound to execute, and the remote call does not necessarily, and the calling message may not be sent to the service party because of network reasons.
- A local call throws only the exception that is declared by the interface, and the remote call also runs out of other exceptions when the RPC framework runs.
- The performance of local and remote calls can vary greatly, depending on the proportion of RPC intrinsic consumption.
It is these differences that determine the need for more consideration when using RPC. When calling the remote interface to throw an exception, the exception could be a business exception, or it could be a run-time exception thrown by the RPC framework (such as a network outage, etc.). A business exception indicates that the service party has made a call, possibly due to a failure to perform properly for some reason, while the RPC runtime exception may not be executed at all, and the exception handling policy for the caller naturally needs to be differentiated.
Because RPC inherently consumes several orders of magnitude higher than local calls, the intrinsic consumption of local calls is nanosecond, while the intrinsic consumption of RPC is at the millisecond level. It is not appropriate for too lightweight computing tasks to export the remote interface is serviced by a separate process, and it is worthwhile to export the service to the remote interface only if the time spent on the computation task is much higher than the intrinsic consumption of RPC.
Summarize
At this point we present a conceptual framework for RPC implementations and detailed analysis of some of the implementation details that need to be considered. No matter how elegant the concept of RPC, but "there are still a few snakes in the grass hidden", only a deep understanding of the nature of RPC, can be better applied.
Http://daodaoliang.com/blog/2015/08/27/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BARPC (%e6%b7%b1%e5%85%a5%e7%af%87). Html
rpc--in-depth article (reproduced)