Redis as a Distributed Cache on .NET 6.0
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker. Well-known as a “data structure server”, with support for strings, hashes, lists, sets, sorted sets, streams, and more. Not only those, but also properties like Programmability, Extensibility, Persistence, Clustering, High availability makes Redis the most popular caching data store. Although today we’re talking about caching with Redis, It is can also be used as a real-time data store and also used for streaming & messaging. Redis is a way of implementing distributed caching for a web application. We talked about In Memory Caching last time, where I promised I’ll talk about distributed caching as well.
Without further a due we’ll start. But before that you need to have few prerequisites. (And don’t worry if you don’t know how to work with docker, we will not go deep into it, however I highly recommend you to learn it and use it for your development work because it is a very useful tool.)
☑ Docker Desktop installed
☐ .NET SDK 6.0 installed
☐ VS Code installed
Before we start, I highly recommend you to read my post about In Memory Caching, because it will introduce you to basic concepts of caching and some technical terms.
Source code for this entire project can be found here:
🏠Creating a Local Redis Server Using Docker
After installing Docker open up a terminal and execute,
docker run --name redis-local -p 5002:6379 -d redis
redis-local
is the name that I’m giving to my local Redis instance. You can type any name you like here. Next the -p
is the port mapping, it will map localhost:5002
to the redis docker container’s localhost:6379
(default Redis port number). -d runs container in background and print container ID.
⚠ Note: For the ease of accessing Redis from other containers via Docker networking, the “Protected mode” is turned off by default. This means that if you expose the port outside of your host (e.g., via -p
on docker run
), it will be open without a password to anyone. It is highly recommended to set a password (by supplying a config file) if you plan on exposing your Redis instance to the internet. Read more.
Now if you open up docker container list you will see it running. (Also you can use docker container ls
command to see container list).
⚠ Note: for the moment we’ll do it on a local Redis server, I’ll show you later how to use Redis with Microsoft Azure. You can have it somewhere else as well. All you need is a connection string to that.
Now let’s login to the redis-cli
running inside of the container. First command is docker exec -it redis-local sh
, which will present us the shell on the container, from the shell we can login to the Redis database using redis-cli
command.
Now if you execute dbsize
command you will get an output like (integer) 0
, meaning that there’s no data.
⚙️ Application Configuration for Redis
For this, I have created a demo ASP.NET MVC project from dotnet-cli using dotnet new mvc
First we need to install 2 Nuget packages. StackExchange.Redis and Microsoft.Extensions.Caching.StackExchangeRedis. [Check .csproj]
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
dotnet add package StackExchange.Redis
Next in the Program.cs add caching information to the dependency container, add the connection string to the appsettings.json
Notice the options.InstanceName
, it’s going to be your application name and it’s gonna prepend any tag. Remember that Redis is a key value pair, your keys must be unique so if you are are shoving stuff into the database from multiple applications the odds that you use the same key in more than one application goes up. To reduce the chance of having two identical keys from two application, you must add an Instance
name prefix so that each key is different. Connection string is localhost:5002
, because we said docker to map Redis port 6379 to local port 5002.
Now we need 2 helper methods to make our lives a bit easier, because with this we can configure few options and provides an abstraction layer to the Redis server.
This is an extension method, that’s why we have this
keyword in front of IDistributedCache
Here we have two methods, SetRecordAsync
and GetRecordAsync
. GetRecordAsync
is simple, we pass the IDistributedCache
and the record Id, then we lookup for the said recordId
, using GetStringAsync(IDistributedCache, String, CancellationToken)
If no record is found for the given key, we are returning the default
of T
, which means we return the default value of the expected type. If we’re expecting an integer
value, default will be 0
, if we’re expecting a List<Model>
, default will be null
. If a record is available in the cache then deserialize the record and return.
Let’s look at the SetRecordAsync as well, there we also pass the IDistributedCache
and the recordId
(new record will be set using this Id), Next the data
of type T
(custom type/ generic type, because the items that we want to store in the cache are of different types, there are not always belong to the same type). Next absoluteExpireTime
and slidingExpireTime
, which I describe in the In Memory Caching on .NET 6 article. Please read the section “Expiration”. In here, notice that our cache expires in 60 seconds.
Inside the method, we set the 2 expiration times using DistributedCacheEntryOptions()
, then serialize the data into JSON format, set the cache using SetStringAsync()
. You can copy and paste this class to your application, because we have used generic types.
🚀Caching in Action
To simulate the database I have created a UserRepository, which will return a list of Users.
To use distributed caching you need to inject IDistributedCache
to the Controller.
Now all you have to do is call those 2 caching helper methods in your Controllers or Razor pages, adhering to the following flow when data is requested.
Notice that recordKey
here is the recordId
for the CacheHelper
class that we’re using to distinguish records, which includes current date and time.
I have added some extra implementation to the Index()
method so that changes are clearly visible in the View
, such as where we got the data from (from cache or database) and some styling. See my completed implementation here.
Now when you run the application for first time, there won’t be any data items in the cache, so data will be loaded from the database. For the next reload data will we available in the cache and will be served from there.
Now if you open redis-cli
inside the docker container and run scan 0
(iterates the set of keys in the currently selected Redis database)(see more), you will see the stored keys in the format that we specified. e.g: “RedisDemo_Users_20220416_0849”
Use hgetall keyname
to see list of fields and their values stored in the hash.
Note the this key has sldexp
(sliding expiration), absexp
(absolute expiration) and data
fields in it. Download the demo project and play around with it.
🔌 About Azure Cache for Redis
Azure Cache for Redis can be used as a distributed data or content cache, a session store, a message broker, and more. It can be deployed as a standalone. Or, it can be deployed along with other Azure database services, such as Azure SQL or Cosmos DB.
Azure Cache for Redis improves application performance by supporting common application architecture patterns. Some of the most common include the following patterns:
If you want to move your Redis server to the cloud, create a Redis database on Microsoft Azure, you can follow Microsoft guide to create a Redis cache.
After you create a cache, all you need is the connection string. It will look something like this:
redis-server-name.redis.cache.windows.net:6380,password=auto-generated-pwd,ssl=True,abortConnect=False
redis-server-name
will be what you have specified while creating the Redis cache. Add this to the appsettings.json
, just like how we did for the local Redis server.
⚠ Note: since Azure Redis instance has a password, don’t keep this in the source control. Using secrets.json in advised.
You can see that many requests (simulated using multiple browsers), now use the same cache and since now your cache server is standalone, you don’t have to worry about the scalability of your application, web server is not impacted by the cache growth.
I’m gonna wrap things up there. Hope you learned something new. Happy coding! 👋