
Monitoring Redis key events in .NET Core
Key event subscriptions for DEL, SET, EXPIRED key events in Redis using .NET Core
Some time ago I wrote an article about Reloading the cache automatically once expired is ASP.NET MVC. The piece of code represented in that article is build on top of .NET Framework 4.7.2. It works fine in case you have a single instance of the application as System.Web.HttpRuntime.Cache relies on in memory caching and therefor it is bond to the same VM where application instance is running. This is fine as long as you are running only one application instance. Once horizontal scaling and increasing of instances kicks in due to high traffic, this approach hits the wall.
In case of scaled and distributed applications, in-memory caching is not that much of a use. In order to keep cache state consistency among the instances you have to switch to distributed type of caching. The first thing that comes into the scope is for sure Redis.
Redis is much more than a simple cache with automatic key expiry. Starting from version 2.8.0, Redis supports something called Redis Keyspace Notifications.
Switching on Redis Keyspace Notifications in Redis
By default Redis Keyspace Notifications feature is off. This means there will be no notifications sent to the subscribed clients on any key change.
Redis notifications is working in fire and forget manner. This means that in case the client is for some reason disconnected once the notification is published, it will not receive notification upon the reconnecting. In simple words, notifications are not persisted or queued. Because of this it is not the most reliable to use them directly and you should consider some workarounds for more reliable delivery if required for your specific case.
To switch on the key namespace notifications in Redis you need to modify the redis.config file or through redis-cli using CONFIG SET command. Depending on which key events you want to create notifications for, the command parameter will defer
E Keyevent events, published with __keyevent@<db>__ prefix.
g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
$ String commands
l List commands
s Set commands
h Hash commands
z Sorted set commands
x Expired events (events generated every time a key expires)
e Evicted events (events generated when a key is evicted for maxmemory)
A Alias for g$lshzxe, so that the "AKE" string means all the events.
Since we want to achieve same functionality as in the article Reloading the cache automatically once expired is ASP.NET MVC but with distributed cache storage, we'll only create notifications for key expiry.
Once the command is executed, Redis will start sending notification to all subscribed clients when keys expire in the Redis database.
Subscribing to Redis notifications in .NET Core C# code
In Microsoft docuemntation related to Distributed caching in ASP.NET Core, NuGet package StackExchange.Redis is used, so we'll stick to it and uset it to subscribe to the key notifications of our Redis instance. For the simplicity reason, I used a simple .NET Core Console application to monitor key events in Redis.
To make things easier for testing on local, I spun up Redis instance in Docker
And just set the configuration to publish key expiry notifications
Now before we write any code, we need to reference the StackExchange.Redis NuGet package in .NET Core project.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.2</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="StackExchange.Redis" Version="2.0.601" /> </ItemGroup> </Project>
We are ready now to write the actual notification subscription code which will register a delegate to be invoked once notification is pushed from the Redis instance once key gets expired
using StackExchange.Redis; using System; namespace RedisTestsConsoleApp { class Program { static void Main(string[] args) { string EXPIRED_KEYS_CHANNEL = "__keyevent@0__:expired"; String host = "192.168.80.128"; ConnectionMultiplexer connection = ConnectionMultiplexer.Connect(host); ISubscriber subscriber = connection.GetSubscriber(); subscriber.Subscribe(EXPIRED_KEYS_CHANNEL, (channel, key) => { Console.WriteLine($"EXPIRED: {key}"); } ); Console.WriteLine("Listening for events..."); Console.ReadKey(); } } }
Note that that constant EXPIRED_KEYS_CHANNEL is set to __keyevent@0__:expired. This way subscriber is explicitly subscribed to EXPIRED key notification which is set in Redis config with KEx parameter value in CONFIG SET redis-cli command. We can also use __keyevent@0__:* to subscribe to all key events, for example if you set Redis to fire events for generic key notifications set with KEg parameter in Redis config.
Now to test this piece of code, I will add break point to a line in notification handler delegate and run the code from Visual Studio. Second thing is to insert Redis key with a dummy value and set it expiry to 3 seconds with SETEX command in redis-cli.
As soon as key mykey expires after 3 seconds, the breakpoint in the code will be hit.
References
Disclaimer
Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.
Comments for this article