Using the Redis NoSql database with .NET Part 18: Redis as a cache engine in .NET
May 12, 2017 Leave a comment
Introduction
In the previous post we looked briefly at data persistence in Redis. We know that the principal storage for Redis is the memory. It also offers two modes of persistence. The first option is called snapshotting which means that the database is saved to disk periodically. The periodicity can be configured in the Redis config file and we can define multiple options depending on the number of new items in memory. The drawback of snapshotting is that if the Redis service abruptly goes down then some unsaved data in memory will be lost. If you cannot afford any data loss then the append-only persistence mode can be for you. It saves the commands in an append-only file which is processed when Redis starts up to restore the database based on the saved commands. Note that append-only will make write operations slower.
In this post we’ll look at caching in the ServiceStack.Redis client. This post also concludes the series on Redis with .NET.
Caching in Redis
When people first come into contact with Redis they often get the impression that it’s “only” a cache engine to store keys and values. By now we know that this is not true and Redis can be used like a “normal” database. Although that requires a lot of planning and creative usage of the Redis data types in the client due to the lack of queries like “where person.age > 40”, the lack of data integrity, primary and secondary keys, schemas etc. However, Redis can of course be used as a cache engine. Indeed, since a Redis database can sit on any remote server and multiple Redis servers can be clustered, Redis is an excellent option for a distributed cache setup. Since the cached data doesn’t sit on the web server it isn’t lost if the web application is redeployed or the web server is restarted. Also, the cached data is in safe hands in a Redis cluster. If one cluster member goes down the others will still be able to serve up the requested cached value.
The ICacheClient interface is the primary way of performing cache-related functions. Note that Redis itself has no specific section or commands just for caching. Cached keys and values are still stored as strings with the SET command in the background. ICacheClient of the ServiceStack.Redis library is really just a convenient helper interface to make the programmer’s life easier when working with caching. You can run the MONITOR command in a redis-cli window while testing the following commands.
We’ll start by probably the most basic operation: setting and getting a cached value by a key:
private static void TryRedisCaching() { IRedisClientsManager pooledClientManager = new PooledRedisClientManager(0, "127.0.0.1:6379"); using (var cacheClient = pooledClientManager.GetCacheClient()) { bool set = cacheClient.Set<string>("cache-test-key", "cache-test-value"); string cachedValue = cacheClient.Get<string>("cache-test-key"); } }
We save “cache-test-value” for the key “cache-test-key” and then retrieve it. The Set function returns a boolean which normally should be true meaning the value was successfully cached.
We can cache objects as well. Here’s an example with a Person object we saw earlier:
bool setObject = cacheClient.Set<Person>("current-person", new Person() { Id = 123, IsFriend = true, Name = "Elvis Presley", Address = new Address() { City = "Memphis", Number = 1, Street = "Graceland" } }); Person cachedPerson = cacheClient.Get<Person>("current-person");
Set has two overloads to set an expiration time on the cached value. Either with a time span…:
bool set = cacheClient.Set<string>("cache-test-key", "cache-test-value", TimeSpan.FromMinutes(30));
…or with a date time:
bool set = cacheClient.Set<string>("cache-test-key", "cache-test-value", DateTime.UtcNow.AddMinutes(30));
This will execute a command in Redis that we haven’t seen before: PEXPIREAT which sets a time-to-live on a key.
We can remove a single key from the cache…:
cacheClient.Remove("cache-test-key");
…or remove many of them at once with a sequence of strings:
cacheClient.RemoveAll(new List<string>() { "cache-test-key", "current-person" });
The generic Add command is very similar to Set. Add will generate the following Redis command:
cacheClient.Add<string>("cache-test-key", "cache-test-value");
“SET” “cache-test-key” “\”cache-test-value\”” “NX”
The NX flag indicates that Redis should only set the key if it does not exist.
We can get and set multiple values with the SetAll and GetAll functions:
cacheClient.SetAll<int>(new Dictionary<string, int>() { { "age:1", 30 }, { "age:2", 40 }, { "age:3", 45 } }); IDictionary<string,int> multipleCache = cacheClient.GetAll<int>(new List<string>() { "age:1", "age:2", "age:3" });
SetAll generates an MSET command, whereas GetAll executes an MGET. We saw both of them in action before in this series.
With Replace we can assign a new value for a key:
cacheClient.Replace<int>("age:1", 60);
It executes a SET command with the XX flag which means that Redis should only set the key if it already exists.
Finally we have the Increment and Decrement functions that generate the INCRBY and DECRBY Redis commands to add or subtract a value from a numeric field:
long newAge2 = cacheClient.Increment("age:2", 12); long newAge3 = cacheClient.Decrement("age:3", 8);
That’s it really, that wasn’t too difficult. We can see that there are no special commands in Redis only meant for caching. It is up to the Redis client to present an interface with cache-friendly functions like getting and setting cache keys or specifying their expiry.
We’ve reached the end of this introductory series on Redis. I think we can conclude that Redis is really not difficult to get going with. Designing a full-scale database using the Redis datatypes is certainly a different challenge from designing tables and relationships in a relational database. On the other hand it provides very quick data access and is also a perfect candidate for distributed caching.
You can view all posts related to data storage on this blog here.