Clear caches on CD instances in Azure PaaS

An introduction to Azure Service Bus

Azure PaaS is a wonderfull platform to host your Sitecore solution. It provides maximum flexibility to outscale your individual components.

In a typical scaled setup (XM or XP) the CM and CD are splitted. The CD server is usually outscaled to multiple instances. The downside of this is, that you are not able to browse to the individual instance like in an IaaS solution, but you are always browsing over Azure and loadbalanced to one of the instances. This all works fine, but when you to do some small admin interventions on your running instances (like manually clearing the cache), this can be an obstacle.

Azure Service Bus

Enter Azure Service Bus. This cloud based middleware adds message queues and message topics to your solution.

Message Queue

With a message queue, you can let one application post a message on a queue, and another application can pick up and process this message. This is asynchronous and the application don’t have to be aware of each other.

In our case the CM would be the sender, and the various CD instances are the consumers. When trying to clear the cache, we would post a message on the queue and one of the consumers would pick up this message and act upon it. (In our case, clear the cache)

Message Topics

However, in our problem case, we want to clear the cache on all the CD servers, so all the instances should process that one message.

This can be done be using Message Topics.

By using a topic, each consumer (=CD instance) when booting creates a subscription on the topic queue and starts listening for messages.

When the sender (CM) send a message to clear the queue, the message gets copied to all of the subscriptions and all of the consumers will process this one message.

Tutorial

Now, in practice, how can we set this up?

First of all you need to create a Azure Service Bus component in Microsoft Azure.

You need the standard pricing tier as a minimum to support topics.

Next, grab the connectionstring from the shared access policies in Azure.

Next step is to dive into the code.

First we need to create a method to send a message on the queue. (You can create a page on your cm to call this method)

public async Task SendClearCacheMessage()
{
    var topicClient = new TopicClient(Settings.ServiceBusConnectionString, Settings.CacheClearTopicName);
    await topicClient.SendAsync(new Message()); 
    await topicClient.CloseAsync();
}

Next we need to create a messagehandler. This class will create a subscription on the topic queue on start up and handle messages posted on the topic.

    public class CacheClearMessageHandler
    {
        private ISubscriptionClient subscriptionClient;

        public void RegisterOnMessageHandlerAndReceiveMessages()
        {
            Task.Run(async () => await CreateSubscription());
            var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler)
            {
                MaxConcurrentCalls = 1,
                AutoComplete = false
            };
            subscriptionClient = new SubscriptionClient(Settings.ServiceBusConnectionString, Settings.CacheClearTopicName, GetInstanceName());
            subscriptionClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
        }

        private async Task CreateSubscription()
        {
            var client = new ManagementClient(Settings.ServiceBusConnectionString);
            if (!await client.SubscriptionExistsAsync(Settings.CacheClearTopicName, GetInstanceName()))
            {
                var subscription = await client.CreateSubscriptionAsync(new SubscriptionDescription(Settings.CacheClearTopicName, GetInstanceName()));
                subscription.AutoDeleteOnIdle = TimeSpan.FromMinutes(10);
                await client.UpdateSubscriptionAsync(subscription);
            }
        }

        private async Task ProcessMessagesAsync(Message message, CancellationToken token)
        {
            ServiceLocator.ServiceProvider.GetService<ICacheService>().ClearCache();
            Sitecore.Diagnostics.Log.Info($"Cleared cache on {GetInstanceName()}",this);
            await subscriptionClient.CompleteAsync(message.SystemProperties.LockToken);
        }

        private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs exceptionReceivedEventArgs)
        {
            Sitecore.Diagnostics.Log.Error("Could not process clear cache request.", exceptionReceivedEventArgs.Exception, this);
            return Task.CompletedTask;
        }
        
        private static string GetInstanceName()
        {
            return Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID")?.Substring(0,50)??"default";
        }

    }

Finally, we need to launch the messagehandler class on startup.

        public override IServiceCollection RegisterServices(IServiceCollection serviceCollection)
        {
            serviceCollection = base.RegisterServices(serviceCollection);
            serviceCollection.AddSingleton<ICacheService, CacheService>();
            new CacheClearMessageHandler().RegisterOnMessageHandlerAndReceiveMessages();
            return serviceCollection;
        }

Leave a Reply