fbpx

Sincronizando cache local com Redis Pub/Sub

Redis

O Redis é um poderoso serviço de armazenamento de dados em memória e intermediador de mensagens.

Se você desenvolve aplicações de larga escala, certamente já fez uso de cache para diminuir o tempo de resposta em alguma rotina que dependia de banco de dados ou arquivos em disco. Se precisou distribuir os dados em cache é bem provável que tenha usado o Redis.

Hoje quero mostrar como sincronizar cache local em aplicações de múltiplas instâncias com um recurso que poucos utilizam: o message broker do Redis, conhecido também como Redis Pub/Sub.

O exemplo que vou dar é bem simples. Teremos dois programas que vão atuar como Publisher e Subscriber de mensagens. O programa que vamos chamar de Subscriber irá assinar um tópico do Pub/Sub do Redis. O segundo, chamado Publisher, vai publicar mensagens para o tópico assinado pelo primeiro programa, o nosso Subscriber. Toda vez que a mensagem for publicada pelo Publisher, o Subscriber irá exibí-la na tela, com data e hora.

Para comunicação com o Redis, vou usar a biblioteca StackExchange.Redis.

Criando o Subscriber

O primeiro passo é configurar e fazer a conexão com o Redis e, após a conexão, obter uma instância de ISubscriber que irá disponibilizar os recursos do Pub/Sub.

using StackExchange.Redis;

namespace Subscriber
{
    class Program
    {
        private static ISubscriber CreateSubscriber()
        {
            var redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
            {
                EndPoints = {
                     { "localhost", 6379 }
                },
            });

            var subscriber = redis.GetSubscriber();

            return subscriber;
        }
    }
}

Em seguida, assinar o tópico que chamei de MyFirstTopic que irá tratar todas as mensagem publicadas para este tópico.

using System;
using StackExchange.Redis;

namespace Subscriber
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Subscribers");
            Console.WriteLine();

            CreateSubscriber()
                .Subscribe("MyFirstTopic", (channel, message) =>
                {
                    Console.WriteLine(message);
                });

            Console.ReadKey();
        }

        private static ISubscriber CreateSubscriber()
        {
            var redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
            {
                EndPoints = {
                     { "localhost", 6379 }
                },
            });

            var subscriber = redis.GetSubscriber();

            return subscriber;
        }
    }
}

Por fim, salvar a mensagem recebidas em memória cache.
O armazenamento da mensagem será simples. Vou guardar o texto da mensagem e usar a data/hora como identificador da mensagem em um dicionário. Após salvar em cache, mostrar as mensagens para vermos as mensagens salvas.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.Caching.Memory;
using StackExchange.Redis;

namespace Subscriber
{
    class Program
    {
        private const string CacheKey = "Messages";

        private static readonly IMemoryCache cache = new MemoryCache(new MemoryCacheOptions());

        static void Main(string[] args)
        {
            Console.WriteLine($"Subscribers");
            Console.WriteLine();

            CreateSubscriber()
                .Subscribe("MyFirstTopic", (channel, message) =>
                {
                    SaveMessage(message);
                    ClearConsole();
                    ShowMessages();
                });

            Console.ReadKey();
        }

        private static void SaveMessage(RedisValue message)
        {
            var messages = cache.GetOrCreate(CacheKey, i => new Dictionary<DateTime, string>());

            messages.Add(DateTime.Now, message);

            cache.Set(CacheKey, messages);
        }

        private static void ClearConsole()
        {
            Console.Clear();
        }

        private static void ShowMessages()
        {
            var messages = cache.Get<IDictionary<DateTime, string>>(CacheKey);
            foreach (var item in messages)
            {
                Console.WriteLine($"{ item.Key }: { item.Value }");
            }
        }

        private static ISubscriber CreateSubscriber()
        {
            var redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
            {
                EndPoints = {
                     { "localhost", 6379 }
                },
            });

            var subscriber = redis.GetSubscriber();

            return subscriber;
        }
    }
}

Criando o Publisher

O próximo passo vai ser criar o Publisher. O programa será responsável por publicar a mensagem para o tópico MyFirstTopic, o tópico que Subscriber assinou lá no início. A lógica também é bem simples. O Publisher vai esperar uma mensagem ser digitada e, após a tecla enter, publicar a mensagem.

using StackExchange.Redis;
using System;

namespace Publisher
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Publisher");
            Console.WriteLine();

            var subscriber = CreateSubscriber();

            do
            {
                Console.Write("Type a message: ");

                var value = Console.ReadLine();

                if (value.ToLower() == "exit")
                {
                    break;
                }

                subscriber.Publish("MyFirstTopic", value);

            } while (true);
        }

        private static ISubscriber CreateSubscriber()
        {
            var redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
            {
                EndPoints = {
                    { "localhost", 6379 }
                },
            });

            var subscriber = redis.GetSubscriber();

            return subscriber;
        }
    }
}

A rotina está envolvida por um Do..While para permitir que outras mensagens possam ser digitadas durante a execução. Também coloquei um tratamento para sair da estrutura de repetição, caso o usuário digite “exit”.

Resultado

Como você pode notar, abri cada programa em abas separadas. Na primeira, deixei o Publisher executando para enviar mensagens, enquanto as observava chegando na aba do Subscriber.
Aqui temos o Subscriber recebendo a mensagem e a exibindo com a data e hora.
Você também pode simular um cenário distribuído, executando mais de um processo do Subscriber. Você vai notar que todas os processos serão notificados sempre que uma mensagem for publicada.

Conclusão

O Redis é uma ferramenta muito poderosa e um ótimo aliado na construção de aplicações de alta disponibilidade.

Em alguns cenários, a melhor forma de solucionar um problema é mantendo os dados em memória local,  em puro objeto CLR. Pois, é uma forma de evitar o custos de latência e serialização/desserialização de objetos. Usar o Redis Pub/Sub para evitar esses custos e obter maior performance pode ser uma ótima alternativa.

O código completo da implementação está no Github.

Até a próxima!

Receba meu conteúdo no Telegram

Se você deseja receber outros conteúdos direto no seu celular, entre no meu canal no Telegram.
Lá eu compartilho dicas para você dominar definitivamente a escrita do código limpo.

Hey,

o que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *