Efficient Microservices Communication: Redis Pub/Sub Message Broker Implementation Guide
Redis Pub/Sub

Efficient Microservices Communication: Redis Pub/Sub Message Broker Implementation Guide

In a microservices architecture, a message broker is an essential component that enables seamless data transfer between different services. Popular options like RabbitMQ and Kafka are commonly used to facilitate this communication. Another robust tool that can be utilized as a message broker is Redis. Apart from being a widely used distributed cache, Redis can function as a primary database or a message broker, as discussed in a previous article (1). This article explores how Redis can serve as a powerful message broker using its Pub/Sub pattern, enhancing the efficiency of inter-service communication in a microservices architecture. While Redis offers multiple patterns like Redis Stream and Redis Message Queue for data transfer between services, our focus here will be on the Pub/Sub pattern.

Redis Pub/Sub simplifies real-time communication between microservices, providing a straightforward yet robust mechanism for publishing and subscribing to messages. Future articles will delve into the other Redis patterns, but for now, let’s delve into how Redis Pub/Sub can streamline microservices communication.

What Pub/Sub pattern?is?

Based on Microsoft’s explanation:

Enable an application to announce events to multiple interested consumers asynchronously, without coupling the senders to the receivers. (2)

So, the process of sending messages between services is like the below diagram.

Image 01?—?Redis Pub/Sub pattern

Based on the above explanation, Redis pub/sub is a messaging pattern where senders(publishers) send messages to the channels and subscribers receive messages from these channels. In Redis, this pattern is implemented using the PUBLISH, SUBSCRIBE, UNSUBSCRIBE, and PSUBSCRIBE commands.

  • PUBLISH: Sends a message to a specific channel.

  • SUBSCRIBE: Subscribes to a channel to receive messages.
  • UNSUBSCRIBE: Unsubscribe from a channel.
  • PSUBSCRIBE: Subscribes to channels matching a pattern.
  • PUNSUBSCRIBE: Unsubscribes from channels matching a pattern.

Pros and Cons

Redis Pub/Sub, like any other architectural pattern, comes with its own set of advantages and disadvantages. Let's discuss them to better understand their suitability for various use cases:

?Advantages

  • Decoupling: Pub/Sub allows for loose coupling between publishers and subscribers. Services can produce and consume messages without needing to know each other, promoting better isolation and scalability.
  • Scalability: It supports multiple subscribers for a single channel, allowing for horizontal scaling of microservices. This makes it easier to handle large volumes of messages and distribute the workload.
  • Real-time Communication: It provides real-time messaging capabilities, making it suitable for scenarios where timely notifications or updates are required.
  • Reliability: Redis provides persistence options for messages, ensuring that messages are not lost even if subscribers are offline when messages are published.
  • Flexibility: Supports both pattern-based (PSUBSCRIBE) and channel-based subscriptions, allowing for flexible message routing and filtering.
  • Performance: Redis is an in-memory data store, which provides high throughput and low latency, making it suitable for high-performance messaging systems.

Disadvantages

  • Message Durability: While Redis can persist messages, it’s not designed as a long-term storage solution. If durability is critical, additional measures like Redis RDB/AOF persistence or integration with other durable message queues might be necessary.
  • Message Ordering: Redis Pub/Sub does not guarantee message ordering across different channels or subscribers. If strict message ordering is required, additional logic or a different messaging system might be needed.
  • Complexity: As the number of channels and subscribers grows, managing and monitoring the system’s state and behavior can become complex.
  • No Message Replay: Once a message is consumed by a subscriber, it’s removed from the channel. If message replay is needed, additional implementation logic is required.
  • Limited to In-Memory: While Redis is highly performant due to its in-memory nature, it might not be suitable for scenarios requiring large message queues or where persistence beyond memory is essential.

Prerequisites

  • Visual Studio or VSCode
  • C#
  • Redis
  • Docker Desktop
  • Postman/ Swagger

Scenario

In this scenario, two services will be implemented. One is responsible for passing a message (Publish) and another service will receive (Subscribe) that message.

Image 02?—?Process diagram

Step 1

In the first step, create 2?.NET API projects using the below command.

dotnet new webapi ProducerService
dotnet new webapi ConsumerService        

Then create a class library that plays a message broker role using the below command.

dotnet new classlib -o RedisBroker        

Now, add the above class library to both services.

dotnet add ProducerService reference RedisBroker/RedisBroker.csproj
dotnet add ConsumerService reference RedisBroker/RedisBroker.csproj        

Now, add Redis packages inside the above class library using the Nuget package manager console or using?.NET CLI.

Install-Package StackExchange.Redis
dotnet add package StackExchange.Redis        

Step 2

In this step, within the RedisBroker class library, create a new class named RedisMessageBroker.cs. Within this class, implement methods to serve as a publisher and subscriber, facilitating communication between our services. Below are the methods you should add to establish this functionality.

using StackExchange.Redis;

public class RedisMessageBroker
{
    private readonly ConnectionMultiplexer _redis;
    private readonly ISubscriber _subscriber;
    public RedisMessageBroker(string connectionString)
    {
        _redis = ConnectionMultiplexer.Connect(connectionString);
        _subscriber = _redis.GetSubscriber();
    }

    // Publish message
    public async Task<bool> Publish(string channel, string message)
    {
        try
        {
            await _subscriber.PublishAsync(RedisChannel.Literal(channel), 
                                                                message);
            return true;
        }
        catch
        {
            return false;
        }
    }

     // Subscribe message
    public async Task<bool> Subscribe(string channel, Action<RedisChannel,                                                                                                                                            RedisValue> handle)
    {
        try
        {
            await subscriber.SubscribeAsync(RedisChannel.Literal(channel),                               handle);
            return true;
        }
        catch
        {
            return false;
        }
    }
}        

As shown in the above code, 2 methods (PublishAsync/SubscribeAsync) have 2 parameters.

Channel: The channel name that passes data through it between services.

Messages (Publisher method): Contains the message which is going to pass

Handle (Subscriber method): Contains the value that comes through the channel which is the message.

Step 3

Create a Models directory in both ProducerService and ConsumerService projects, then add a new Message.cs class file inside each model's directory. Include the necessary properties in both Message.cs files for consistent communication between the projects.

public class Message
{
    public int Id { get; set; } 
    public string Body { get; set; }
}        

Step 4

Add a DBConfig directory to both projects’ root, then create a new RedisConfig.cs class file within each DBConfig directory. Include the necessary Redis configuration properties in both RedisConfig.cs files for consistent database configuration across the projects.

public class RedisConfig
{
    public string RedisCacheURL { get; set; }
}        

Now, inside the appsettings.json file of both projects, add the below connection string that is related to our Redis instance which is running through the Docker on the local machine.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "RedisCacheURL": "127.0.0.1:6379"
  },
  "AllowedHosts": "*"
}
        

Now, register Redis insde the program.cs file in both projects.

using Microsoft.Extensions.Options;
using ProducerService.DBConfig; //inside ProducerService
using ConsumerService.DBConfig; //inside ConsumerService
using RedisBroker;

// Register Redis as a message broker
// Best practice: Use DI in the RedisBroker class library to have a clean- // code section inside the Program.cs file

builder.Services.Configure<RedisConfig>(builder.Configuration.GetSection("ConnectionStrings"));
builder.Services.AddSingleton<RedisMessageBroker>(sb =>
{
    var config = sb.GetRequiredService<IOptions<RedisConfig>>().Value;
    return new RedisMessageBroker(config.RedisCacheURL);
});        

Step 5

It is time to generate a service to pass data to the Redis Pub/Sub. So, in the root directory of the ProducerService project, create a new directory called MessageService, and inside it, create a new interface called IMessageService.cs.

using ProducerService.Models;

public interface IMessageService
{
    Task<Message> SendMessage(Message message);
}        

Now, inside the same directory, create a new class called MessageService.cs.

using ProducerService.Models;
using RedisBroker;
using System.Text.Json;

public class MessageService : IMessageService
{
    private readonly RedisMessageBroker _redisMessageBroker;
    public MessageService(RedisMessageBroker redisMessageBroker)
    {
        _redisMessageBroker = redisMessageBroker;
    }

    public async Task<Message> SendMessage(Message message)
    {
        var res = await _redisMessageBroker.Publish("MessageChannel", JsonSerializer.Serialize(message));
        if(res is true)
        {
            return message;
        }
        return null;
    }
}        

Note: This is the implementation of IMessageService and the interface will be injected into the controller to pass data between services using RedisMessageBroker that has been implemented inside the class library. So, to have more cleaner structure, it is better to use clean code architecture to maintain and manipulate everything easily.

Now, register this service, to program.cs file in the ProducerService project.

using ProducerService.MessageService;

// Register Message service
builder.Services.AddScoped<IMessageService, MessageService>();        

Step 6

Here, a controller will be required to get data from the client and pass it to the Redis Publisher. So, in the root directory of the ProducerService project, inside the Controller directory, create a new controller called MessageController.cs and add the following action.

using Microsoft.AspNetCore.Mvc;
using ProducerService.MessageService;
using ProducerService.Models;

[Route("api/[controller]")]
[ApiController]
public class MessageController : ControllerBase
{
    private readonly IMessageService _messageService;
    private readonly ILogger<MessageController> _logger;

    public MessageController(IMessageService messageService, ILogger<MessageController> logger)
    {
        _messageService = messageService;
        _logger = logger;
    }

    [HttpPost]
    public async Task<IActionResult> Post(Message message)
    {
        var res = await _messageService.SendMessage(message);
        _logger.LogInformation($"{DateTime.Now.ToShortDateString()} : {res.Body}");
        return Ok(res);
    }
}        

As shown in the above code, to keep everything straightforward, the base model which is Message.cs passes directly as an argument of the Post action. It is better to use a DTO object and a mapping function or a mapping library like AutoMapper, Mapster, etc. (3)

After passing data, it is time to receive the message inside the ConsumerService project.

Step 7

To receive incoming messages, create a new directory in the root directory of the ConsumerService project called Services, create a new class called ConsuerService.cs, and add the following method.

using ConsumerService.Models;
using StackExchange.Redis;
using System.Text.Json;

public class ConsumerServices
    {
        private readonly ILogger<ConsumerServices> _logger;

        public ConsumerServices(ILogger<ConsumerServices> logger)
        {
            _logger = logger;
        }

        public void HandleMessage(RedisChannel redisChannel, RedisValue message)
        {
            var messageRes = JsonSerializer.Deserialize<Message>(message);
            _logger.Log(LogLevel.Information, messageRes.Body);
        }
    }        

Now, register ConsumerService,cs inside the Program.cs file.

using ConsumerService.Services;

builder.Services.AddSingleton<ConsumerServices>();        

Step 8

Now, the channel should be subscribed by the ConsumerService project. So, inside the program.cs file, add the following code to listen to the channel to receive incoming messages.

var app = builder.Build();

var messageBroker = app.Services.GetRequiredService<RedisMessageBroker>();
var consumerService = app.Services.GetRequiredService<ConsumerServices>();

await messageBroker.Subscribe("MessageChannel", (channel, message) =>
{
    consumerService.HandleMessage(channel, message);
});        

Finally, it is time to test microservices and Redis pub/sub-broker. So, run the docker desktop and start the Redis instance.

Image 03?—?Running Redis on Docker Desktop

Then, send a request to the API endpoint using the Postman.

Image 04?—?Send a post request

Now, a message has been sent to the pub/sub-Redis broker.

Image 05?—?Redis Pub/Sub

As shown, in the above image, the message has existed inside the Redis channel.

Image06?—?Log of the incoming message

As indicated, the message has been received and the log has been demonstrated on the API console.

Conclusion

Utilize this implementation as an effective means of implementing a broker for services that broadcast data, such as notification or alarm services. To ensure robustness and data consistency, consider integrating the SAGA pattern(4) and EventSourcing(5) in your services.

Github

GitHub Repository

YouTube

YouTube Video

References

(1) https://blog.devops.dev/redis-distributed-caching-implementation-in-net-d1232b1ef987

(2) https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber

(3) https://medium.com/@vahidalizadeh1990/crud-operation-by-repository-pattern-using-net-6-ef-core-sql-server-mysql-mongodb-part-2-25532829b79d

(4) https://blog.stackademic.com/implementation-of-saga-orchestration-using-masstransit-dd238530f0d7

(5) https://medium.com/devops-dev/event-sourcing-implementation-in-net-microservices-e0b83c8331e5


Exciting possibilities ahead with Redis Pub/Sub. vahid alizadeh

要查看或添加评论,请登录

Vahid Alizadeh的更多文章

  • Redis Distributed Caching implementation in .NET

    Redis Distributed Caching implementation in .NET

    Efficient caching implementation in .NET applications will be explored in this article, with a focus on the compelling…

  • Unit testing based on xUnit in .NET

    Unit testing based on xUnit in .NET

    This article will delve into the critical practice of unit testing in software development and explore the benefits it…

  • Event sourcing implementation in .NET microservices

    Event sourcing implementation in .NET microservices

    This article will cover Event-Sourcing architecture in .NET 7 and almost everything required for developing services…

    2 条评论
  • Using RabbitMQ in .NET microservices

    Using RabbitMQ in .NET microservices

    In this article, RabbitMQ implementation will be discussed, and figured out how it is used in a microservices…

    2 条评论
  • Microservices and MassTransit

    Microservices and MassTransit

    In this article, MassTransit implementation will be discussed, and how it could share messages between .NET…

    4 条评论
  • Make REST API using gRPC in the?.NET?Core

    Make REST API using gRPC in the?.NET?Core

    In this article, the implementation of gRPC services will be covered, and it will be figured out how they can replace…

  • Implementation of Saga orchestration using MassTransit

    Implementation of Saga orchestration using MassTransit

    n this article, we will explore the Saga orchestration pattern and demonstrate how it works through a simple scenario…

    2 条评论

社区洞察

其他会员也浏览了