RPC
Modern RPC
When compared with REST (using gRPC as example)
Comparison table
|
| |
Definition | REST is an architecture style. It exposes data as resources and CRUD operations could be used to access resources. HTTP is an implement conforming to REST styles | Make the process of executing code on a remote machine as simple and straight-forward as calling a local functions. There are many types of RPC. RPC usually exposes action-based API methods. gRPC is a multi |
Use case | Cross-language platform, public and private facing scenarios | Cross-language platform, public scenarios |
Serilization protocol | readablee text(XML, JSon) | Use ProtoBuf by default |
Transmission protocol | Typically on HTTP1.1 | HTTP 2.0 which supports streaming communication and bidirectional support. |
API contract | Loose, Optional (Open API) | Strict, required (.proto) |
Delivery semantics | Idempotent | At most/least/exactly once |
User friendly | Easy to debug because request/response are readable | Hard to debug because request/response are not readable |
Browser support | Universal browser support. | Limited browser support. gRPC requires gRPC-web and a proxy layer to perform conversions between HTTP 1.1 and HTTP 2. |
Code generation support | Developers must use a third-party tool like Swagger or Postman to produce code for API requests. | gRPC has native code generation features. |
HTTP verbs | REST will use HTTP methods such as GET, POST, PUT, DELETE, OPTIONS and, hopefully, PATCH to provide semantic meaning for the intention of the action being taken. | RPC uses only GET and POST, with GET being used to fetch information and POST being used for everything else. |
Examples | SpringMVC/Boot, Jax-rs, drop wizard | Dubbo, Motan, Tars, gRPC, Thrift |
Semantics of RPC
At least once
Def: For every request message that the client sends, at least one copy of that message is delivered to the server. The client stub keeps retrying until it gets an ack. This is applicable for idempotent operations.
Exactly once
Def: For every request message that the client sends, exactly one copy of that message is delivered to the server.
But this goal is extremely hard to build. For example, in case of a server crash, the server stub call and server business logic could happen not in an atomic manner.
At most once
Def: For every request message that the client sends, at most one copy of that message is delivered to the server.
Designs
How to detect a duplicate request?
Client includes unique transaction ID with each one of its RPC requests
Client uses the same xid for retransmitted requests
How to avoid false detection?
One of the recurrent challenges in RPC is dealing with unexpected responses, and we see this with message IDs. For example, consider the following pathological (but realistic) situation. A client machine sends a request message with a message ID of 0, then crashes and reboots, and then sends an unrelated request message, also with a message ID of 0. The server may not have been aware that the client crashed and rebooted and, upon seeing a request message with a message ID of 0, acknowledges it and discards it as a duplicate. The client never gets a response to the request.
One way to eliminate this problem is to use a boot ID. A machine’s boot ID is a number that is incremented each time the machine reboots; this number is read from nonvolatile storage (e.g., a disk or flash drive), incremented, and written back to the storage device during the machine’s start-up procedure. This number is then put in every message sent by that host. If a message is received with an old message ID but a new boot ID, it is recognized as a new message. In effect, the message ID and boot ID combine to form a unique ID for each transaction.
How to ensure that the xid is unique?
Combine a unique client ID (e.g. IP address) with the current time of the day
Combine a unique client ID with a sequence number
Combine a unique client ID with a boot ID
Big random number
seen and old arrays will grow without bound
Client could tell server "I'm done with xid x - delete it".
Client includes "seen all replies <= X" with every RPC
Server may crash and restart
If old[], seen[] tables are only in meory, then the user needs to retry
Last of many
Last of many : This a version of 'At least once', where the client stub uses a different transaction identifier in each retransmission. Now the result returned is guaranteed to be the result of the final operation, not the earlier ones. So it will be possible for the client stub to tell which reply belongs to which request and thus filter out all but the last one.
Sample Dubbo RPC implementation
Skeleton RPC design
RPC framework (wrapping Registry center, client, server.)
Serialization
Factors to consider
Support data types: Some serialization framework such as Hessian 2.0 even support complicated data structures such as Map and List.
Cross-language support
Performance: The compression rate and the speed for serialization.
General RPC protocol vs specialized RPC protocol:
Prefer general RPC protocol because the parameters / methods / marshall / etc could be any type.
Protobuf
Compatibility
Each field will be decorated with optional, required or repeated. optional keyword helps with compatibility. For example, when a new optional field is added, client and server could upgrade the scheme independently.
Efficiency
Protobuf is based on varied length serialization.
Tag, Length, Value
Tag = (field_num << 3) | wire_type
field_num is the unique identification number in protobuf schema.
Hessian2
Def: Self-descriptive language. Avoids generating the client and server side stub and proto.
Use case: Default serialization scheme in Dubbo.
Sample design
Transport
Netty basics
Implementation based on Netty. Netty listens to the following types of events:
Connection event: void connected(Channel channel)
Readable event: void sent(Channel channel, Object message)
Writable event: void received(Channel channel, Object message)
Exception event: void caught(Channel channel, Throwable exception)
Http 1.1 vs Http 2
REST APIs follow a request-response model of communication that is typically built on HTTP 1.1. Unfortunately, this implies that if a microservice receives multiple requests from multiple clients, the model has to handle each request at a time, which consequently slows the entire system. However, REST APIs can also be built on HTTP 2, but the request-response model of communication remains the same, which forbids REST APIs to make the most out of the HTTP 2 advantages, such as streaming communication and bidirectional support.
gRPC does not face a similar obstacle. It is built on HTTP 2 and instead follows a client-response communication model. These conditions support bidirectional communication and streaming communication due to gRPC's ability to receive multiple requests from several clients and handle those requests simultaneously by constantly streaming information. Plus, gRPC can also handle "unary" interactions like the ones built on HTTP 1.1.
gRPC is able to handle unary interactions and different types of streaming. The following are sampleSample gRPC interface definition
Sample design
Command
Response Header:
Payload:
InFlightRequests
Used to capture all requests going on.
Within the InflightRequests there is a semaphore implementation because:
In synchronous programming, client will only send another requests when it receives the current one.
In asynchronous programming, server will immediately return a response so there must be some concurrency control in place.
Netty channel
Used to send request.
Generate the stub
Static compiling
During compiling interface definition files
For example in gRPC, gRPC will compile IDL files into gRPC.java stub files.
Example code for generating stub according to static resource
There is only one interface method to implement.
StubFactory:
Implementation with DynamicStubFactory.
createStub():
AbstractStub:
InvokeRemote() is a method.
RpcRequest is the parameter to the above method.
SPI mechanism: Dynamically create concrete impl of an interface.
A more lightweight version of dependency injection.
NettyAccessPoint does not create an instance of DynamicStubFactory directly.
Dynamical bytecode instrument
Dynamically generating the stub (bytecode instrument, please see this chart https://github.com/DreamOfTheRedChamber/system-design-interviews/blob/master/MicroSvcs_Observability.md#bytecode-instrumentation). For example Dubbo. Java class files have some fixed structure according to JVM.
Example code for generating stub dynamically
The generated class file sun.misc.ProxyGenerator.saveGeneratedFiles
Server
Server processing model
BIO: Server creates a new thread to handle to handle each new coming request.
Applicable for scenarios where there are not too many concurrent connections
NIO: The server uses IO multiplexing to process new coming request.
Applicable for scenarios where there are many concurrent connections and the request processing is lightweight.
AIO: The client initiates an IO operation and immediately returns. The server will notify the client when the processing is done.
Applicable for scenarios where there are many concurrent connections and the request processing is heavyweight.
Reactor model: A main thread is responsible for all request connection operation. Then working threads will process further jobs.
Service discovery
Please see Service discovery
Choose RPC framework
Cross language RPC: gRPC vs Thrift
|
|
|
Integrated frameworks | Lose -_- | Borned earlier. Integrate with big data processing frameworks such as Hadoop/HBase/Cassandra. |
Supported num of languages | Lose -_- | Support 25+ languages, more than gRPC |
Performance | Used more often in mobile scenarios due to Protobuf and Http2 utilization. Generated code smaller than thrift. | Lose -_- |
Same language RPC: Dubbo (Motan/Tars) vs Spring Cloud
|
|
|
Supported languages | Java (Java/C++) | Java |
Supported functionalities | Dubbo(Motan/Tars) is only RPC protocol | Spring cloud provides many other functionalities such as service registration, load balancing, circuit breaker. |
gRPC
Interface definition language
The steps are as follows:
Programmer writes an interface description in the IDL (Mechanism to pass procedure parameters and return values in a machine-independent way)
Programmer then runs an IDL compiler which generates
Code to marshal native data types into machien independent byte streams
Client/server stub: Forwards local procedure call as request to server / Dispatches RPC to its implementation
HTTP 1.1 vs HTTP 2
Transport over HTTP/2 + TLS
First, gRPC runs on top of TCP instead of UDP, which means it outsources the problems of connection management and reliably transmitting request and reply messages of arbitrary size.
Second, gRPC actually runs on top of a secured version of TCP called Transport Layer Security (TLS)—a thin layer that sits above TCP in the protocol stack—which means it outsources responsibility for securing the communication channel so adversaries can’t eavesdrop or hijack the message exchange.
Third, gRPC actually, actually runs on top of HTTP/2 (which is itself layered on top of TCP and TLS), meaning gRPC outsources yet two other problems:
Binary framing and compression: Efficiently encoding/compressing binary data into a message.
Multiplexing: Requests by introducing concept of streams.
HTTP: The client could send a single request message and the server responds with a single reply message.
HTTP 1.1: The client could send multiple requests without waiting for the response. However, the server is still required to send the responses in the order of incoming requests. So Http 1.1 remained a FIFO queue and suffered from requests getting blocked on high latency requests in the front Head-of-line blocking
HTTP2 introduces fully asynchronous, multiplexing of requests by introducing concept of streams. lient and servers can both initiate multiple streams on a single underlying TCP connection. Yes, even the server can initiate a stream for transferring data which it anticipates will be required by the client. For e.g. when client request a web page, in addition to sending theHTML content the server can initiate a separate stream to transfer images or videos, that it knows will be required to render the full page.
gRPC use cases
As mentioned, despite the many advantages gRPC offers, it has one major obstacle: low browser compatibility. Consequently, gRPC is a bit limited to internal/private systems.
Contrarily, REST APIs may have their disadvantages, as we have discussed, but they remain the most known APIs for connecting microservices-based systems. Plus, REST follows the HTTP protocol standardization and offers universal support, making this API architectural style a top option for web services development as well as app and microservices integrations. However, this does not mean we should neglect gRPC's applications.
gRPC architectural style has promising features that can (and should) be explored. It is an excellent option for working with multi-language systems, real-time streaming, and for instance, when operating an IoT system that requires light-weight message transmission such as the serialized Protobuf messages allow. Moreover, gRPC should also be considered for mobile applications since they do not need a browser and can benefit from smaller messages, preserving mobiles' processors' speed.
History
The biggest differences between gRPC and SunRPC/DCE-RPC/RMI is that gRPC is designed for cloud services rather than the simpler client/server paradigm. In the client/server world, one server process is presumed to be enough to serve calls from all the client processes that might call it. With cloud services, the client invokes a method on a service, which in order to support calls from arbitrarily many clients at the same time, is implemented by a scalable number of server processes, each potentially running on a different server machine.
The caller identifies the service it wants to invoke, and a load balancer directs that invocation to one of the many available server processes (containers) that implement that service
Multi-language, multi-platform framework
Native implementations in C, Java, and Go
Platforms supported: Linux, Android, iOS, MacOS, Windows
C/C++ implementation goals
High throughput and scalability, low latency
Minimal external dependencies
Last updated