Cache 缓存在计算机领域是一个被普遍使用的概念。硬件中 CPU 有一级缓存,二级缓存, 浏览器中有缓存,软件开发中也有分布式缓存 memcache, redis。缓存无处不在的原因是它能够极大地提高硬件和软件的运行速度。在项目开发中,性能慢的地方常常是 IO操作频繁的地方,读取数据库是我们常见的消耗性能的地方。这个时候,如果将使用频繁的数据缓存到能够高速读取的介质中,下次访问时不用再去请求数据库,从缓存中获取所需的数据,能大大提高性能。这篇文章主要讨论的是在.Net开发中,如何使用CacheManager 框架方便的管理项目中的缓存。

1>_ CacheManager 介绍以及优点
CacheManager 是开源的.Net缓存管理框架。它不是具体的缓存实现,而是在缓存之上,方便开发人员配置和管理各种不同的缓存,为上层应用程序提供统一的缓存接口的中间层。下面是 CacheManager 的一些优点:

  • 让开发人员的生活更容易处理和配资缓存,即使是非常复杂的缓存方案。
  • CacheManager 能够管理多种缓存,包含 内存, appfabric, redis, couchbase, windows
    azure cache, memorycache 等。
  • 它提供了额外的功能,如缓存同步、并发更新、事件、性能计数器等……

2>_ CacheManager开始之旅
CacheManager上手还是非常简单的。下面使用内存缓存结合CacheManager的一个实例,能够帮助我们快速的熟悉CacheManager如何使用。首先在Visual Studio中创建一个Console Application。

使用 Nuget 为项目添加 CacheManager 引用。CacheManager 包含了很多的Package。其中 CacheManager.Core 是必须的,其它的针对不同缓存平台上有不同的对应 Package。这个 Demo 中,我们使用内存作为缓存,所以只是需要 CacheManager.Core 和CacheManager.SystemRuntimeCaching

接着在Main函数中配置好我们的缓存:

using System;
using CacheManager.Core;
namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var cache = CacheFactory.Build("getStartedCache", settings =>
            {
                settings.WithSystemRuntimeCacheHandle("handleName");
            });
        }
    }
}

上面代码中使用 CacheFactory 创建了一个名称为 getStartedCache 的缓存实例,这个缓存实例使用的是 SystemRunTime Cache, 内存缓存。一个缓存实例是可以配置多个Handle的,我们可以使用内存来作为存储介质,也可以使用 Redis 分布式缓存作为存储介质,并且可以同时在一个缓存实例中使用,后面我们再介绍多级缓存的配置和使用。

接下来,我们添加一些测试缓存的代码:

static void Main(string[] args)
{

    var cache = CacheFactory.Build("getStartedCache", settings =>
    {
        settings.WithSystemRuntimeCacheHandle("handleName");
    });

    cache.Add("keyA", "valueA");
    cache.Put("keyB", 23);
    cache.Update("keyB", v => 42);
    Console.WriteLine("KeyA is " + cache.Get("keyA"));      // should be valueA
    Console.WriteLine("KeyB is " + cache.Get("keyB"));      // should be 42
    cache.Remove("keyA");
    Console.WriteLine("KeyA removed? " + (cache.Get("keyA") == null).ToString());
    Console.WriteLine("We are done...");
    Console.ReadKey();
}

3>_ CacheManager 多级缓存配置
实际开发中,我们常常会需要使用多级缓存。

一种常见的情况是,你有一个分布式式缓存服务器,例如 edis,独立的缓存服务器能够让我们的多个系统应用程序都能够共享这些缓存的数据,因为这些缓存项的创建是昂贵的。和访问数据库相比,分布式缓存速度较快,但是和内存相比,还是不够快。因为分布式缓存使用还需要序列化和网络传输的时间消耗。这个时候里,做个分级缓存是个好的解决方案,将内存缓存结合分布式缓存使用,使用频率高的数据直接从内存中读取,这将大大提高应用程序的整体性能。

使用内存缓存的读取速度能够达到分布式缓存的100倍,甚至更高。

使用 CacheManager, 配置多级缓存是一件非常容易的事情

var cache = CacheFactory.Build<int>("myCache", settings =>
{
    settings
        .WithSystemRuntimeCacheHandle("inProcessCache")//内存缓存Handle
        .And
        .WithRedisConfiguration("redis", config =>//Redis缓存配置
        {
            config.WithAllowAdmin()
                .WithDatabase(0)
                .WithEndpoint("localhost", 6379);
        })
        .WithMaxRetries(1000)//尝试次数
        .WithRetryTimeout(100)//尝试超时时间
        .WithRedisBackPlate("redis")//redis使用Back Plate
        .WithRedisCacheHandle("redis", true);//redis缓存handle
});

上面代码中,内存缓存和 Redis 缓存配置部分很容易看明白。但是 BackPlate 是什么作用? 接下来,我们看看 CacheManager 中的BackPlate 挡板机制。

4>_ BackPlate解决分布式缓存中的同步问题

对于大型的软件系统,常常都是分为很多独立的子项目,各个子项目为了节约成本或者是方便数据共享,常常会共用同一个分布缓存服务器。这样在使用多级缓存的时候,就有可能出现数据不一致的情况。
假设在系统A中的更新了缓存中的一个数据项,这个时候CacheManager会在A设置的所有的缓存handle中更新改数据,这里也包括了分布式缓存上的数据。但是在系统B中的内存缓存中,还是会存在着旧的未更新的数据。当系统B从缓存中取这条记录的时候,就会出现内存缓存和分布式缓存中的数据不一致的情况。
为了防止这一点,缓存管理器有一个功能叫做 cachebackplate 将尝试同步多个系统中的缓存。
上面设置的多级缓存中,我们就将 redis 作为 BackPlate 的源. 也就是说所有的数据都需要以 redis 中缓存的数据为蓝本。
在设置 redis 作为 BackPlate 之后,同样发生上面的数据不一致的情况的时候,只要 redis 中的数据被修改了,就会触发CacheManager 更新所有系统中的内存缓存中的数据,和redis中的数据保持一致。

同步的工作是如何完成的?

每次一条缓存记录被删除或更新的时候,Cache Manager 会发送一个消息,让 BackPlate 存储这次的数据变化信息。所有其它的系统将异步接收这些消息,并将相应地作出更新和删除操作,保证数据的一致性。

5>_ ExpirationMode 和 CacheUpdateMode

涉及到缓存,就必然有缓存过期的问题。CacheManager中提供了一些简单的缓存过期方式设置。

public enum ExpirationMode
{
    None = 0,
    Sliding = 1,
    Absolute = 2,
}

同时 CacheManager 还为多级缓存之间设置不同的数据更新策略

public enum CacheUpdateMode
{
    None = 0,
    Full = 1,
    Up = 2,
}

使用 Sliding 和 Up, 我们我可以为多级缓存设置不同的缓存过期时间,这样使用频率高的数据就能够保存在访问速度更快的内存中,访问频率次高的放到分布式缓存中。 当CacheManager 在内存中找不到缓存数据的时候,就会尝试在分布式缓存中找。找到后,根据 Up 设置,会再将该缓存数据保存到内存缓存中。

具体的配置方式如下:

var cache = CacheFactory.Build<int>("myCache", settings =>
{
    settings.WithUpdateMode(CacheUpdateMode.Up)
        .WithSystemRuntimeCacheHandle("inProcessCache")//内存缓存Handle
        .WithExpiration(ExpirationMode.Sliding, TimeSpan.FromSeconds(60)))
        .And
        .WithRedisConfiguration("redis", config =>//Redis缓存配置
        {
            config.WithAllowAdmin()
                .WithDatabase(0)
                .WithEndpoint("localhost", 6379);
        }).
        .WithExpiration(ExpirationMode.Sliding, TimeSpan. FromHours  (24)))
        .WithMaxRetries(1000)//尝试次数
        .WithRetryTimeout(100)//尝试超时时间
        .WithRedisBackPlate("redis")//redis使用Back Plate
        .WithRedisCacheHandle("redis", true);//redis缓存handle

});

6>_ 缓存使用分析

在缓存使用中,对于缓存hit和miss数据态比较关系,这些数据能够帮助我们分析和调整缓存的设置,帮助缓存使用地更加合理。

var cache = CacheFactory.Build("cacheName", settings => settings

在配置好缓存的Statistic功能后,我们就能够跟踪到缓存的使用情况了, 下面就是分别打印各个缓存handle中的分析数据。

foreach (var handle in cache.CacheHandles)
{
    var stats = handle.Stats;
    Console.WriteLine(string.Format(
            "Items: {0}, Hits: {1}, Miss: {2}, Remove: {3}, ClearRegion: {4}, Clear: {5}, Adds: {6}, Puts: {7}, Gets: {8}",
                stats.GetStatistic(CacheStatsCounterType.Items),
                stats.GetStatistic(CacheStatsCounterType.Hits),
                stats.GetStatistic(CacheStatsCounterType.Misses),
                stats.GetStatistic(CacheStatsCounterType.RemoveCalls),
                stats.GetStatistic(CacheStatsCounterType.ClearRegionCalls),
                stats.GetStatistic(CacheStatsCounterType.ClearCalls),
                stats.GetStatistic(CacheStatsCounterType.AddCalls),
                stats.GetStatistic(CacheStatsCounterType.PutCalls),
                stats.GetStatistic(CacheStatsCounterType.GetCalls)
            ));

7>_ 结语

缓存是个好东西,用好了能够极大的提高性能。缓存的使用本身是个很大的话题,这边文章只是从缓存管理这个角度介绍了CachManager 的使用。
下面是 CacheManager 相关的资料和链接:

官方主页
http://cachemanager.net/
源代码
https://github.com/MichaCo/CacheManager
官方MVC项目的Sample
https://github.com/MichaCo/CacheManager/tree/master/samples/CacheManager.Samples.Mvc