A comparison of microservice communication frameworks

Thiru
5 min readMar 28, 2022

In this article, we compare the following communication frameworks:

  • RESTful API
  • gRPC
  • RSocket
  • Apache Dubbo
Communication protocols/frameworks

Background: Why compare these frameworks?

I am working on a use case where a service (say client) needs to synchronously communicate with another service (say server), also this client communicates very frequently with the server

I started looking at some of the popularly used communication frameworks, and wanted to make a comparison of these frameworks to select the best framework that meet the needs of this use case

Following are some of the criteria for the framework to support this use case

  • Offers better performance
  • Supports load balancing
  • Provides API in Java
  • Should have less dependencies
  • Maturity of the API: better documentation, library maturity, maintainability are few other aspects that I wanted to consider

Introduction

A quick introduction to these frameworks/protocols are given below, you can find further details on these topics in the references section.

RESTful API

RESTful API is an application programming interface that conforms to the constraints of REST architectural style and allows for interaction with RESTful web services.

In this article, we use Spring RestTemplate on the client side and Spring RestController on the server side for comparison.

gRPC

gRPC is a modern open source high performance Remote Procedure Call framework that can run in any environment. It can efficiently connect services in and across data centres with pluggable support for load balancing, tracing, health checking and authentication

RSocket

RSocket is a binary protocol for use on byte stream transports such as TCP, WebSockets, and Aeron.

RSocket provides a protocol for Reactive Streams semantics between client-server, and server-server communication.

In this article, we use RSocket with TCP for comparison.

Apache Dubbo

Apache Dubbo is a high-performance, java based open source RPC framework.

Serialization

We used the following serialization formats for comparison in this article, which can provide a little insights into performance of these serialization formats

  • Protobuf
  • JSON
  • Dubbo/Hessian

Since gRPC uses Protobuf serialization format by default, to make a fair comparison of these frameworks, I have used Protobuf serialization with other communication protocol/frameworks such as RSocket & RESTful API.

Protobuf

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data — it is smaller, faster, and simpler

JSON

JavaScript Object Notation (JSON) is a lightweight data-interchange format. It is easy for humans to read and write

Hessian

Hessian 2.0 provides cross-language binary object serialization with efficiencies better than java.io serialization

All the examples in this article are written in Java

Note

  • Some of these frameworks provide APIs in other languages.
  • In this article, we use only synchronous request/response style of communication for comparison.
  • Other communication styles (non-blocking/streaming/reactive etc.) are supported by some of these frameworks, which shall be compared in a future article.
  • JSON & Protobuf serialization support were added to the test application APIs (wherever feasible)

Test application

To compare these frameworks, I had developed chat-app a simple client and server application (as the communication is very frequent)

The chat-app consists of two modules chat-server and chat-client

chat-server

  • provides API to create/update/find/remove a message
  • provides the above API implementations using RSocket, gRPC, Apache Dubbo & RESTful frameworks
  • a message is a simple text with an identifier and creation timestamp
  • stores the messages in memory (using a ConcurrentHashMap)

chat-client

  • invokes server side APIs using one of the client side implementation using RSocket, gRPC, Apache Dubbo, RESTful API

The chat-client application sends a series of individual CRUD instructions (create, update, find, delete) to the server for measuring the performance of these operations

The chat-app code used in this article is available in the following location

A sample code snippets for client and server are shown below

Note: There are four client and server implementations available, here we show snippets only for two of them

chat-client code snippet

A snippet of code for RSocket client is shown below

@Override
public Message create(Message message) {
Mono<Message> messageMono = rSocketRequester
.route(MessagePaths.CREATE_MESSAGE)
.data(message)
.retrieveMono(Message.class);
return messageMono.block();
}

@Override
public boolean update(Long id, Message message) {
message.setId(id);
Mono<Status> resultMono = rSocketRequester
.route(MessagePaths.UPDATE_MESSAGE)
.data(message)
.retrieveMono(Status.class);
return resultMono.block().isSuccess();
}
@Override
public Optional<Message> findById(Long id) {
Mono<Message> messageMono = rSocketRequester
.route(MessagePaths.FIND_MESSAGE_BY_ID)
.data(id)
.retrieveMono(Message.class);
return messageMono.blockOptional();
}
@Override
public boolean remove(Long id) {
Mono<Status> resultMono = rSocketRequester
.route(MessagePaths.REMOVE_MESSAGE_BY_ID)
.data(id)
.retrieveMono(Status.class);
return resultMono.block().isSuccess();
}

chat-server code snippet

A snippet of code for gRPC server is shown below

@Override
public void create(Message request, StreamObserver<Message> responseObserver) {
var message = messageService.create(request);
responseObserver.onNext(message);
responseObserver.onCompleted();
}

@Override
public void update(Message request, StreamObserver<Status> responseObserver) {
var status = messageService.update(request);
responseObserver.onNext(status);
responseObserver.onCompleted();
}

@Override
public void findById(MessageID request, StreamObserver<Message> responseObserver) {
var message = messageService.findById(request);
responseObserver.onNext(message);
responseObserver.onCompleted();
}
@Override
public void removeById(MessageID request, StreamObserver<Status> responseObserver) {
var status = messageService.remove(request);
responseObserver.onNext(status);
responseObserver.onCompleted();
}

Test environment

  • OS: Windows 10
  • Language: Java 11
  • Memory: 32 GB RAM
  • Processor : Intel(R) Core(TM) i7–8850H CPU @ 2.60GHz 2.59 GHz

In the test setup, both the client and server were run from the same machine

Performance test

We measured the total time taken for running 100k operations i,e. each CRUD operation was executed for 25k times

Results

Following table shows the total time taken in milliseconds for executing 100,000 operations.

Performance test results

Note: These test results were taken from the clients, written using default (out of box) configuration

You can clearly notice an anomaly in the results for RestTemplate (RESTful API Client).

I noticed that POST calls with Spring RestTemplate (using default configuration) are very slow and takes ~92% of the total time, while GET, PUT & DELETE operations work normally.

I re-tested the Spring RestTemplate (RESTful client) with a custom Request Factory and the results were normal

RestTemplate with custom request factory

Which one did we pick?

  • We decided to use RESTful clients for communication and there are couple of reasons for this decision
  • (1) Performance of Spring RestTemplate is not far behind from RSocket’s performance.
  • (2) We did not want to introduce additional dependencies to our existing server application

Summary

  • RSocket has the best performance when compared with other frameworks used in this article
  • New projects that require frequent communication can take a look at RSocket or gRPC
  • gRPC has better documentation and works very well out of the box
  • Protobuf offers slightly better performance over JSON
  • HTTP POST calls with Spring rest templates are slow when used with default configuration

References

--

--