gRPC
Streaming
Inter-Instance Communication
Network Communication
Distributed Systems

gRPC streaming inter instance communication

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

gRPC (gRPC Remote Procedure Calls) is an open-source framework initially developed by Google that enables service-to-service communication. It enhances the traditional RPC mechanisms to leverage modern features like bidirectional streaming, pluggable authentication, and balanced load distribution, making it especially suitable for building distributed applications and microservices.

Understanding gRPC Streaming

gRPC offers four types of method APIs:

  1. Unary (simple request/response)
  2. Server streaming
  3. Client streaming
  4. Bidirectional streaming

For the purpose of inter-instance communication, our focus is primarily on the streaming APIs—server streaming, client streaming, and bidirectional streaming. These APIs allow continuous communication where messages are streamed as a sequence rather than a single request or response action.

  • Server Streaming: The client sends a request to the server and gets a stream to read a sequence of messages back. The client reads from the returned stream until there are no more messages.
  • Client Streaming: The client writes a sequence of messages and sends them to the server, again using a provided stream. Once the client has finished writing the messages, it waits for the server to read them and return a response.
  • Bidirectional Streaming: In this method, both client and server send a sequence of messages using a read-write stream. This type of streaming is particularly useful for situations where the client and the server need to send multiple messages back-and-forth dynamically.

Technical Implementation

Setting up

To implement gRPC, you generally start by defining your service and the message types it uses in a Protocol Buffers (protobuf) file. This step is crucial as it defines the structure of the communication.

For example, here’s a simple service description that supports bidirectional streaming:

protobuf
1syntax = "proto3";
2
3package chat;
4
5// The chat service definition.
6service ChatService {
7  // Sends a message to the chat server and receives stream of chat messages
8  rpc chat(stream ChatMessage) returns (stream ChatMessage);
9}
10
11// The message being sent over the stream.
12message ChatMessage {
13  string user = 1;
14  string message = 2;
15}

Implementing the service

On the server side, the implementation of this service would involve handling incoming streams and potentially broadcasting the received messages to other connected clients. Here's an example using Python with the gRPC library:

python
1import grpc
2from concurrent import futures
3import chat_pb2
4import chat_pb2_grpc
5
6class ChatService(chat_pb2_grpc.ChatServiceServicer):
7    def Chat(self, request_iterator, context):
8        for new_msg in request_iterator:
9            print(f"Received message from {new_msg.user}: {new_msg.message}")
10            # Here, you would typically process the message and potentially modify the response
11            yield new_msg  # Echoes the received message back
12
13def serve():
14    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
15    chat_pb2_grpc.add_ChatServiceServicer_to_server(ChatService(), server)
16    server.add_insecure_port('[::]:50051')
17    server.start()
18    server.wait_for_termination()
19
20if __name__ == '__main__':
21    serve()

Client implementation

On the client-side, you would open a stream to send messages and simultaneously listen for messages from the server:

python
1import grpc
2import chat_pb2
3import chat_pb2_grpc
4
5def run():
6    with grpc.insecure_channel('localhost:50051') as channel:
7        stub = chat_pb2_grpc.ChatServiceStub(channel)
8        # Creating a bidirectional stream
9        chat_stream = stub.Chat(iter([chat_pb2.ChatMessage(user="user1", message="Hello"),]))
10        for response in chat_stream:
11            print(f"Received: {response.message} from {response.user}")
12
13if __name__ == '__main__':
14    run()

Advantages of Using gRPC for Streaming

AspectDetail
EfficiencyBinary serialization (protobuf) is efficient and compact.
Language InteroperabilityCode generation from .proto files for different languages.
Stream Control FeaturesIn-built flow control and error-handling mechanisms.
ScalabilitySuitable for lightweight microservices architecture.
IntegrationWell-supported by cloud-native environments.

Conclusion and Additional Points

The adoption of gRPC allows for robust, efficient communication between services, utilizing the HTTP/2 protocol's advantages such as multiplexing and server push. While this article has shown basic examples, gRPC supports advanced features like metadata, deadlines, and cancellation, among others, which are especially important in complex distributed systems.

By leveraging gRPC for inter-instance communication, developers can build highly responsive and scalable applications that are well-suited to modern cloud-native environments.


Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.