If you do not have a Redis cluster yet, you can refer to another article by the author: Building a Distributed Redis Cluster and Getting Started with Redis
This article will use the StackExchange.Redis library to connect to and operate Redis.
The use of StackExchange.Redis
in this article is merely a representation of the documentation, and if your English is good, it is recommended to read the documentation: https://stackexchange.github.io/StackExchange.Redis/Basics
This article introduces the basic usage of StackExchange.Redis
and then discusses caching in ASP.NET Core and how to use Redis.
Basics
Redis Library
There are many open-source Redis client libraries for C#, including BeetleX.Redis, csredis, Nhiredis, redis-sharp, redisboost, Rediska, ServiceStack.Redis, Sider, StackExchange.Redis, and TeamDev Redis Client.
Here we will use StackExchange.Redis. Additionally, csredis has been significantly maintained by Ye (the author of Freesql), and Ye has opened a new framework called FreeRedis, which is currently under development. If interested, you can participate in the development or provide suggestions.
Connecting to Redis
Create a .NET Core project and add the [StackExchange.Redis](https://stackexchange.github.io/StackExchange.Redis/)
NuGet library reference, using the latest version.
The default port for Redis is 6379. To connect to a local Redis service:
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379");
// "{ip}:{port}"
If using a Redis cluster, separate the addresses with ,
:
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("server1:port1,server2:port2,server3:port3");
It is essential to distinguish between cluster mode, multiple Redis instance addresses, which suit master-slave mode clusters or Redis cluster; sentinel mode has not been tested by the author.
What can Redis be used for
Redis has many application scenarios, generally used for:
- Storing data (acting as a database)
- Using pub/sub for message queues
The following will introduce the usage methods for these two scenarios.
Redis Database Storage
Access the Redis database:
IDatabase db = redis.GetDatabase();
Redis has 16 databases by default, and you can use GetDatabase(int db)
to access a specific database.
For Redis nodes using Redis cluster, there is only one database and selection is not available. Here we only need to use redis.GetDatabase()
.
Redis is quite simple to use; most of the time, as long as there is a corresponding application scenario, we can quickly grasp it by referring to the documentation. Therefore, only the usage of strings will be introduced here.
Strings
Refer to the Redis strings documentation: https://www.cnblogs.com/whuanle/p/13837153.html#字符串string
IDatabase
contains operations for string data types, and its API starts with String
, making it easily recognizable.
Setting a string data:
db.StringSet("A", "This is a string data value");
var value = db.StringGet("A");
If the string is stored using byte[] (binary), you can also set its value:
byte[] str = ... ...
db.StringSet("A", str);
Redis contains many other types, but here we only introduce strings, as the APIs are quite limited; you can learn other types when needed. Start with string usage, and you can learn others later.
Publish and Subscribe
Subscribe to a topic so that when its state changes, subscribers can receive notifications, facilitating distributed messaging similar to MQTT protocol.
Get the subscriber:
ISubscriber sub = redis.GetSubscriber();
Choose the topic to subscribe to and set a callback function:
sub.Subscribe("Message", (channel, message) => {
Console.WriteLine((string)message);
});
When one party subscribes to Message
, another client (which can also be the same) may publish to the topic:
sub.Publish("Message", "You have a new message, please check.");
After the topic is published, the subscribing party can receive the pushed message.
Testing code:
ISubscriber sub = redis.GetSubscriber();
sub.Subscribe("Message", (channel, message) => {
Console.WriteLine((string)message);
});
Thread.Sleep(1000);
sub.Publish("Message", "You have a new message, please check.");
channel
: The name of the topic, that is, the Message
above.
message
: The content of the pushed message.
RedisValue
When setting values using the API, this parameter is always involved. Since Redis values can only be "strings," C# must conform to this rule. However, C# is a strongly typed language with many value types; using only string can be inconvenient when writing code.
Thus, the RedisValue
type was created, which has numerous implicit conversion overloads, allowing us to use C# simple types to store and retrieve data, avoiding manual conversion.
Of course, this description is not very accurate; the use of RedisValue
primarily considers ease of conversion.
This concludes the introductory knowledge. For more Redis knowledge, you can consult the official documentation. Next, we will introduce the use of distributed caching in ASP.NET Core.
ASP.NET Core Caching and Distributed Caching
ASP.NET Core has many defined standard interfaces, such as logging and caching; these interfaces provide developers with unified definitions and functions, allowing upper services to switch libraries without changing the code, and the underlying library does not affect the upper layer.
Caching in ASP.NET Core can be accomplished in various ways, such as Redis, memory, relational databases, file caching, etc. Moreover, based on scalability, caching can be classified into in-memory cache and distributed cache.
Common in-memory cache is the memory cache, which can store any object. The most common distributed cache is Redis, and the distributed cache interface is limited to byte[]
(the parameter, which will be clarified in the subsequent sections). Both in-memory and distributed caches store cached items using key-value pairs.
In-memory Caching
ASP.NET Core In-memory Cache
The ASP.NET Core in-memory cache is typically used in single-machine scenarios, and this memory caching framework is usually provided by the System.Runtime
or Microsoft packages. Since there is no need to consider distributed or complex structures, third-party libraries are generally unnecessary. Note that in-memory caching refers not only to data in memory but also to distinguish from professional caching frameworks like Redis. Here, caching is used to improve performance.
Two primary implementations of this cache mainly include System.Runtime.Caching
and MemoryCache
, which have richer features.
Caching and Storing Data in Memory
Beyond ASP.NET Core's in-memory caching, we need to discuss if the self-defined in-memory caching is reasonable during coding.
We all know that the use of in-memory caching is for performance improvement.
In this regard, the author believes that this type of cache can be discussed from two levels.
The first is for data that needs to be used multiple times, where each use requires computation, and the source data is the same; the result should also be the same — in this case, in-memory caching can be used. For example, reflection is relatively time-consuming (slow), and in-memory caching can be used so that the information can be retrieved directly without recomputation.
Here are the reasons:
Using in-memory caching on reflection caches can ensure that the data source is determinable and calculable, and this portion of memory does not need to frequently increase or decrease. This not only improves performance but also reduces GC's recovery pressure to some extent, while more importantly, developers can lower the complexity of caching.
This caching is mainly intended to avoid repeated calculations or repeated imports (e.g., loading assemblies, loading data from files), etc. If data has recently appeared and will not change for a while, it is practical to cache it in memory, such as MVC views or leaderboards refreshed every 15 minutes.
The second is that many people simply use memory storage due to its speed, treating it like a database, which can easily lead to memory leaks. The most common situation is using static dictionaries or static lists, managing data through methods for additions, deletions, queries, and modifications; in such cases, under stress testing or higher request volumes with frequent changes, memory can accumulate significantly.
For frequently changing or real-time changing data, storing it in memory indeed provides speed. However, determining data expiration and removing useless data requires deep consideration.
Moreover, when using dictionaries to store substantial amounts of data in memory, the indexing time for accessing data becomes longer. If you use LINQ or for
or foreach
for data retrieval, you may easily encounter long time complexities. In such scenarios, would you trust your code or databases like MySQL or SQLServer? Are you familiar with hash algorithms and red-black trees?
If you must use in-memory caching for data that may dynamically increase or decrease, you can use WeakReference
to allow GC to collect the object while still referencing it. The downside is that data may be lost, making it unsuitable for persistent data.
However, regardless of the situation, we can confirm:
- Caching is a copy
- Cache loss does not affect the use of the program
- Cache cannot grow indefinitely
- Cache avoids complex structures
- ... ...
IMemoryCache
The interface provided by IMemoryCache
is quite limited:
ICacheEntry CreateEntry(object key);
void Remove(object key);
bool TryGetValue(object key, out object value);
It is suitable for singular key-value caching.
This interface is implemented in Microsoft.Extensions.Caching.Memory
, such as MemoryCache
, and is apt for use in ASP.NET Core.
MemoryCache
Here, MemoryCache
does not refer to the implementation of IMemoryCache
, but rather to System.Runtime.Caching.MemoryCache
, which requires the installation of a NuGet package.
It can implement caching for instance objects; please refer to the official documentation: https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.caching.memorycache?view=dotnet-plat-ext-3.1
Additionally, there is a distributed memory cache, but it is not truly distributed; information can be found here: https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed?view=aspnetcore-3.1#distributed-memory-cache
Distributed Caching
ASP.NET Core distributed caching uses the unified interface IDistributedCache
. If you search for IDistributedCache in NuGet, you will find many related libraries.
Regarding the use of distributed caching, apart from the most common Redis, SQL Server is also acceptable, as long as it implements IDistributedCache
, it is good to go.
IDistributedCache
The methods provided by the IDistributedCache
interface are quite limited, consisting of four asynchronous methods and four synchronous methods; here only the asynchronous methods are introduced.
| Method | Description |
| ------------------------------------------------------------- | ------------------------------------------------------------------- |
| GetAsync(String, CancellationToken) | Retrieve the value of a key |
| RefreshAsync(String, CancellationToken) | Refresh a value based on a key in the cache and reset its timeout (if any) |
| RemoveAsync(String, CancellationToken) | Remove a key |
| SetAsync(String, Byte[], DistributedCacheEntryOptions, CancellationToken) | Set the value of a key |
The limitations are still significant; it can only use strings. It is estimated that most people may not have used it much.
Currently, the official ASP.NET Core support for distributed caching includes primarily NCache, Redis, and SQL Server. This section focuses on Redis.
Redis Caching
StackExchange.Redis is the officially recommended Redis framework for ASP.NET Core, and the official has wrapped it. You can search for Microsoft.Extensions.Caching.StackExchangeRedis
in NuGet.
RedisCache
inherits the IDistributedCache
interface.
Configure service registration in Startup.ConfigureServices
:
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "ip:port,ip1:port,ip2:port"; // redis cluster or stand-alone
options.InstanceName = "mvc"; // instance name
});
Dependency injection:
private readonly IDistributedCache _cache;
Example:
public async Task<string> Test(string key, string value)
{
await _cache.SetStringAsync(key, value);
return await _cache.GetStringAsync(key);
}
Setting cache time:
var options = new DistributedCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(20));
await _cache.SetStringAsync(key, value, options);
文章评论