Caching is a common technique aimed at improving the performance and scalability of a system. To cache data, frequently accessed data is temporarily copied into fast storage located close to the application. If this fast data store is closer to the application than the original source, caching can dramatically improve the response time of client applications by serving data more quickly.

cache-aside

Cache Types

Private In-Memory Cache

A private cache means the caching service runs on the same server as the application; it is part of the application, usually a library or module. A private cache is typically an in-memory cache, such as MemoryCache or ConcurrentDictionary. It is a lightweight cache suitable for small applications. The advantage of a private cache is that it is simple and easy to use; the drawback is that data cannot be shared across servers, making it unsuitable for large applications. The IMemoryCache interface can be used to work with a private cache in .NET.

privatecache


services.AddMemoryCache();

public class MyService(IMemoryCache cache) : IService
{
    public async Task Test()
    {
        cache.Set("key", "value");
        var value = cache.Get("key");
    }
}

Distributed Shared Cache

A distributed cache means the caching service runs across multiple servers; it is an independent service that can share data across servers. A distributed cache is typically an in-memory cache such as Redis, Memcached, or Garnet. It is a high-performance, low-latency cache suitable for large applications. The advantages of a distributed cache are high performance and low latency, with support for large-scale data storage and access; the drawbacks are higher complexity and the need for additional server resources. The IDistributedCache interface can be used to work with a distributed cache in .NET.

cache-shared


services.AddMemoryDistributedCache();

public class MyService(IDistributedCache cache) : IService
{
    public async Task Test()
    {
        await cache.SetStringAsync("key", "value");
        var value = await cache.GetStringAsync("key");
    }
}

Using the Redis Distributed Cache

Redis is a high-performance, open source distributed cache system. It is an in-memory database that supports a variety of data structures, such as strings, lists, hash tables, and sets. Redis is designed to provide a high-performance, low-latency distributed cache system that supports large-scale data storage and access while offering high availability and scalability. Redis uses the RESP protocol and is written in C, with support for many languages such as C, C++, C#, Java, Python, and Node.js. In .NET, you can use the open source StackExchange.Redis library to work with the cache.

Redis for Windows is no longer maintained. You can install Redis using WSL2 or Docker on Windows. Memurai can also be used as a Redis alternative; Memurai is a Redis service for Windows that supports most Redis features and lets you run a Redis service on Windows.

Containerized Redis Service

docker run --name my-redis -d -p 6379:6379 -e REDIS_PASSWORD=guest redis

Testing Client Tools

Use redis-cli to connect to the Linux cache server and run the following commands for testing.

redis-cli -h localhost -p 6379 -a Password
auth Password
set key value
get key

Using the StackExchange.Redis Client

donet add package StackExchange.Redis
 using StackExchange.Redis;

 var redis = ConnectionMultiplexer.Connect("localhost:6379,password=guest");
 var db = redis.GetDatabase();

 db.StringSet("key", "value");

 var value = db.StringGet("key");

Using the IDistributedCache Interface

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379,password=guest";
    options.InstanceName = "SampleInstance";
});
using Microsoft.Extensions.Caching.Distributed;

public class MyService(IDistributedCache cache) : IService
{
    public async Task Test()
    {
        await cache.SetStringAsync("key", "value");
        var value = await cache.GetStringAsync("key");
    }
}

Using the IDistributedCache interface is recommended. It is an abstract interface that makes it easy to switch between different cache implementations such as Redis, Memcached, or Garnet, and it supports the ASP.NET Core caching middleware, making it convenient to use caching in ASP.NET Core.

Using Microsoft’s Garnet Distributed Cache

Garnet is an open source distributed cache system from Microsoft. It is a high-performance, low-latency distributed cache that supports a variety of data structures, such as strings, lists, hash tables, and sets. Garnet is designed to provide a high-performance, low-latency distributed cache system that supports large-scale data storage and access while offering high availability and scalability.

Garnet uses the popular RESP protocol and is written in C#. It supports most Redis commands while providing additional features such as cluster setup, data persistence, data replication, and data sharding. In C#, you can use the open source StackExchange.Redis library to work with the cache.

https://microsoft.github.io/garnet/docs

Self-Hosting in a .NET Program

dotnet add package Microsoft.Garnet
using Garnet;

try
{
    using var server = new GarnetServer(args);
    server.Start();
    Thread.Sleep(Timeout.Infinite);
}
catch (Exception ex)
{
    Console.WriteLine($"Unable to initialize server due to exception: {ex.Message}");
}

Running in a Docker Container

docker run --name garnet -d -p 6379:6379 --ulimit memlock=-1 ghcr.io/microsoft/garnet --auth Password --password guest

Running as a Windows Service

dotnet add package Microsoft.Garnet
dotnet add package Microsoft.Extensions.Hosting.WindowsServices

public class GarnetService(ILogger<GarnetService> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        if (!stoppingToken.IsCancellationRequested)
        {
            string[] commandLineArgs = Environment.GetCommandLineArgs();

            using var server = new GarnetServer(commandLineArgs);

            logger.LogInformation("Starting Garnet server...");

            try
            {
                server.Start();
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "An error occurred while starting the Garnet server.");
            }


            logger.LogInformation("Garnet server started.");

            await Task.Delay(Timeout.Infinite, stoppingToken);
        }
    }
}
var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddWindowsService(options =>
{
    options.ServiceName = "Garnet Service";
});

builder.Services.AddHostedService<GarnetService>();

var host = builder.Build();

host.Run();
sc.exe create GarnetService binpath= "C:\Users\GarnetWindowsService.exe" start= auto

sc.exe start GarnetService

sc.exe stop GarnetService

sc.exe delete GarnetService

Using a Distributed Cache in the Basket Service

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
builder.Services.AddStackExchangeRedisCache(options =>
{
     options.Configuration = builder.Configuration.GetConnectionString("MyRedisConStr");
});

High-Availability Distributed Cache Clusters

Building a high-availability cluster with multiple cache servers can improve the availability and reliability of the system.

https://microsoft.github.io/garnet/docs/cluster/overview

https://redis.io/topics/cluster-tutorial


services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379,localhost:6380,localhost:6381";
    options.InstanceName = "SampleInstance";
});

services.AddStackExchangeRedisCache(options =>
{
    options.InstanceName = "SampleInstance";
    options.ConfigurationOptions = new ConfigurationOptions
    {
        Password = "Password",
        AbortOnConnectFail = false,
        EndPoints =
        {
            { "localhost", 6379 },
            { "localhost", 6380 },
            { "localhost", 6381 }
        }
    };
});

Caching Best Practices

https://learn.microsoft.com/zh-cn/azure/architecture/best-practices/caching

https://learn.microsoft.com/zh-cn/azure/architecture/patterns/cache-aside

Caching Concepts

Cache avalanche, cache penetration, cache breakdown, cache warm-up, cache update, cache degradation, cache hit rate, sliding expiration, absolute expiration, no expiration, and cache dependencies.

https://developer.aliyun.com/article/841392

IDistributedCache expiration times fall into two kinds: absolute expiration and sliding expiration.

Distributed Cache Monitoring Tools

The RedisInsight tool can be used to monitor a Redis distributed cache system.

https://redis.io/docs/latest/operate/redisinsight/install

docker run -d --name redisinsight -p 5540:5540 redis/redisinsight:latest

Using the Aspire Component Library for Distributed Caching

Aspire Host Project Configuration

dotnet add package Aspire.Hosting.Redis
var redis = builder.AddRedis("cache");
builder.AddProject<Projects.ExampleProject>().WithReference(redis)

Microservice Project Configuration

dotnet add package Aspire.StackExchange.Redis.DistributedCaching
builder.AddRedisDistributedCache("cache");