Build High Performant Microservices using gRPC and .NET 6

Learn how to leverage server streaming to deliver 5 Million records in a breeze

Hammad Abbasi
Geek Culture
10 min readDec 25, 2021

--

Photo by Fotis Fotopoulos on Unsplash

Microsoft has rolled out its fastest .NET release yet — .NET 6, with long term stable version of .NET Core and lots of new APIs, performance, and language improvements. In this article, we will see what’s new in .NET 6 for gRPC Services (blazing fast performance and serialization, better fault tolerance, client-side load balancing, and HTTP/3 support).

We will also learn how we can leverage these features to build high performant microservice using gRPC and .NET 6. We will also create a real-world gRPC service utilizing server streaming to process and deliver five million records.

Note: If you are new to the gRPC Services in .NET, It’s recommended to go through this article first which explains some basic concepts and also explains how gRPC compares with other technologies like WCF and REST.

A Brief Recap:

  • gRPC is a popular open-source RPC Framework run by CNCF.
  • It’s a contract first language-independent platform — which simply means that client and server have to agree on a contract about what and how the messages will be delivered. The contract is defined in a .proto file which derives the code generation process through the tooling that .NET6 provides.
  • It’s cross-platform so both client and server may use a different technology stack.
  • It uses HTTP/2 and sends Protobuf (a high performant serialization technology for messages). Unlike JSON which stores data in human-readable text format, Protobuf uses the binary-interchange format and isn’t human-readable and hence needs tooling to create strongly typed client and base classes. Luckily with .NET, it’s as easy as referencing any NuGet package.
  • HTTP/2 enables multiplexing where you send multiple requests simultaneously on a single connection.
  • It also supports streaming where a server can send multiple responses to the client and vice versa.
  • Bi-Directional Streaming — where both the client and the server send multiple messages back and forth.
gRPC High-Level Overview (Image by Author)

Creating your first gRPC Service in .NET 6

Without further adieu, Let’s create a first gRPC service in .NET 6.

Prerequisites:

  1. Create a new project and select the ‘ASP.NET Core gRPC’ Service template
gRPC Project Template ( Image by Author)

Make sure .NET 6.0 is selected as a target framework

  • Docker Support can be enabled if you want to run this service in a container.

Clicking on create will create the project with the default greeter service as shown below.

Let’s analyze the greet.proto file

This is a language-neutral contract that simply defines what the service will look like and the messages it consumes.

Let’s look at the GreeterService.cs which is the implementation of this contract (.proto file)

This service simply implements the SayHello action and returns the HelloReply message back to the client. You may notice that types GreeterBase, HelloReply, HelloRequest are not yet defined. If you check the definition of GreeterBase type — you will that these types were auto-generated by .NET6 tooling.

Auto-Generated Types ( Image by Author)

How does the code-generation work?

if you edit the .csproj file you will notice the protobuf element referencing greet.proto and a property indicating server code generation.

To run the service simply open the command prompt and type ‘dotnet run’

You can notice the service is running on https://localhost:7057. Now, unlike REST APIs, we can’t test this on a browser and need to create a client.

Create a new console app to consume the service. Right-click on dependencies and choose ‘Manage Connected Service’

Now, we will add a service reference — Select gRPC.

Locate your proto file and select the ‘Client’ type that will generate the necessary code to consume the gRPC service.

Make sure to verify the properties of greet.proto file to generate ‘Client only’ stub classes.

Now open the program.cs and add the below lines to invoke your gRPC Servi

The above code simply creates a new gRPC channel which is passed to a greeter client which then invokes the SayHello method.

Running the project to see the response from the service.

Now that we have created our first gRPC service in .NET 6, let’s explore some new features and advanced concepts in .NET 6.

What’s new for gRPC in .NET 6

  1. Performance Improvements:
  • In NET 6 ASCII string serialization for protobuf uses SIMD — Single Instruction multiple data to process characters in parallel using high-performance CPU instructions, yielding 20% performance overall improvement.
  • gRPC sends and receives raw bytes using ByteString. The new .NET 6 introduces Zero Copy ByteString API which avoids the need to create a copy /allocate an internal array. This is really helpful if you are sending and receiving large messages to avoid unnecessary allocation.
Image by Author
  • In .NET 5 HTTP/2 library was using fixed buffer size limiting download speed when there’s latency. This issue has been solved in .NET 6 by updating the HTTP/2 library to use dynamic buffer size which improved the download performance by 118%.

2. Transient Fault Handling

You can now catch RPC Exceptions, detect transient faults (like loss of network connectivity or timeouts) and use built-in logic for automatic retries which can now be configured on a channel.

Tip: Check this link If you want to learn more about transient fault handling with gRPC retries.

3. Client-Side Load Balancing

With client-side load balancing, you can have your gRPC clients distribute the load optimally across your servers. It eliminates the need to have a proxy for load balancing. We can configure client-side load balancing while creating a new channel. It consists of two components:

  • Service Discovery: It acts as a resolver and makes a DNS query to get IPs of the server where gRPC is hosted.
  • The load balancer, which creates a connection and picks the address using various configurations such as PickFirst and RoundRobin logic.
Client-Side Load Balancing (Image by Author)

If any of the endpoints fails, the load balancer will automatically switch to the other healthy endpoints.

gRPC — Client Side Load Balancing ( Image by Author)

3. HTTP/3 Support

.NET 6 is the first gRPC implementation to support end-to-end HTTP/3.

HTTP/2 enables multiple streams and uses framing to enable multiple requests to be handled at the same time over the same connection, however, the world has gone mobile using Wi-Fi and cellular connections which can be unreliable at times, and in those cases, the TCP packet will be lost and all of the streams will be blocked — head of line blocking problem. HTTP/3 Solves this by using a new connection protocol called QUIC which uses UDP and has TLS built-in, so it's faster to establish a connection and is independent of the IP address so mobile clients can switch between wifi and cellular networks — keeping the same logical connection.

.NET 6 supports QUIC and has an open-source implementation of it — MSQUIC and provides the following benefits:

  • Faster response time of the first request
  • improved experience when there is connection packet loss
  • support transitioning between networks

Note: The RFC for HTTP/3 is not yet finalized and may change so it’s a preview feature in NET6.

In the next section, we will learn how to leverage gRPC server streaming to build high performant Microservice that delivers 5M records to the client.

If we go back to our greeter service, you will notice that it’s using the unary call — which starts with the client sending a request message and a response message is returned when the service has finished processing.

gRPC Server Streaming

Server streaming enables the gRPC client to send a request to the service and gets a stream of responses. The client reads from the returned stream until there are no more messages. gRPC takes care of the message ordering which is guaranteed.

In our example, we will use this sample CSV file of 196MB, containing 5million sales records. Now, delivering those records will not be efficient in a single call. Furthermore, traditional rest-style paging requires multiple client requests — back and forth communication from client to server.

gRPC Server streaming solves this problem efficiently.

  • The client will be simply invoking the service method.
  • Our gRPC service will be reading from the CSV file using StreamReader line by line, converting the rows to the model gRPC understands, and sending the record back to the client — one row at a time.
  • The Client will be receiving the stream of responses.

let’s begin by defining a proto file to deliver messages with the following fields.

Sample Sales Data (CSV) Fields

Protos-> sales.proto

the stream keyword indicates that the SalesDataModel will be delivered as a stream

Let’s add a new service — SalesaDataService.cs as below

Let’s break it down to understand it better.

The class implements the SalesServiceBase which is autogenerated by .NET6 tooling using proto file configuration.

We then override the method GetSalesData to read the data from the file and return it as a stream by simply writing our data model to the responseStream object as shown below:

Now let’s run the service to see it in action.

gRPC Service ( Image by Author )

The Service is up and running so we can create our client.

Create a new console project, add gRPC service reference using protobuf (as shared previously)

This is how our client implementation looks like

Let’s run the client and you can see the incoming messages stream from your gRPC Service.

gRPC Client Output — 5 million records ( Image by Author )

It takes almost 2minutes to load all 5m sales records from the gRPC service. However, in some cases, you may want to specify for how long a call should run. To do this, you need to configure the deadline.

so when this time exceeds, the client will stop processing stream and throw an exception which can be handled as below:

The below example uses an upper limit of 5 seconds so it only processed 77829 records.

gRPC Client Output — with Deadline ( Image by Author )

Conclusion

In this article, we have learned about new performance enhancements for gRPC .NET 6 like client-side load balancing, transient fault handling, and HTTP/3. It also explains how gRPC Server streaming can be leveraged to build high performant microservice able to process and deliver millions of records.

You can download the source code from this repo.

--

--

Hammad Abbasi
Geek Culture

Innovating Enterprise Applications with AI & LLM | Solution Architect | Tech Writer & Innovator | Bringing Ideas to Life using Next-Gen Tech Innovations