65.9K
CodeProject 正在变化。 阅读更多。
Home

使用共享缓存的高级缓存

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (7投票s)

2009年2月13日

GPL3

6分钟阅读

viewsIcon

63009

downloadIcon

709

本文介绍了如何使用 SharedCache 实现高级缓存。

目录

引言

几周前,我发现了一篇关于缓存的精彩文章(《探索 ASP.NET 中的缓存》),作者是Abhijeet Jana。Abhijeet 的这篇出色文章(谢谢 Abhi)为我学习 ASP.NET 缓存打下了良好的基础。为了延续这个主题,我想介绍一些关于高级缓存的概述和实现细节。

背景

首先,我想简要介绍一下 ASP.NET 缓存的基础知识。ASP.NET 有一个强大的缓存系统,我们可以用它来提高应用程序的响应时间。当一个对象在创建过程中消耗大量服务器资源时,缓存非常有用。当我们缓存一个对象时,它的重用速度比任何其他访问都快。因此,Web 服务器的响应速度比每次从头创建对象时要快得多。

为了在 Web 应用程序中实现缓存,ASP.NET 提供了 Cache 对象。这个对象基于字典的行为,其中缓存的项与一个简单的字符串键相关联,这很像其他语言(例如 Perl)中支持的关联数组

以下代码片段可用于在 ASP.NET 中存储和检索缓存数据:

用于存储

Cache["keyString"] = "Any kind of object, that can be serialized";

用于检索

object cachedValue = Cache["keyString"];

问题

尽管这听起来很美妙,通过一行代码就可以在 Web 应用程序中实现强大的缓存功能。然而,故事并未就此结束。毫无疑问,ASP.NET 缓存为 Web 应用程序开发提供了直观且重要的功能,但它也带来了一些其他复杂问题。例如:

  1. 它增加了 Web 服务器的内存使用量。
  2. 在内存使用量大的情况下,ASP.NET 运行时会自动清除缓存中的项(这不公平)。有关更深入的细节,您可以参考:http://www.mnot.net/cache_docs/

乍一看,这些问题似乎可以忽略不计。对于小型、简单的 Web 应用程序来说,这确实是正确的。但如果我们考虑一个大型企业应用程序,它包含业务层、DAL(数据访问层),有时还有一个单独的领域层(包含所有领域实体),那么选择一种实现分布式缓存和负载均衡功能的高级缓存解决方案就变得显而易见了。总之,缓存解决方案必须提供以下功能:

  • 它必须提供不同级别的缓存
  • 它必须实现不同的清除算法。例如:
    • LRU – 最近最少使用项。
    • LFU – 最不常用项。
    • 基于时间。
  • 它必须提供简单的服务器和客户端配置,并带有自定义提供程序节。
  • 它必须完全免费且开源!

解决方案

我研究了几个开源工具箱,发现了一个功能丰富且免费(并且开源)的解决方案。我找到的解决方案是 SharedCache

现在,我想提供实现 SharedCache 的详细信息。请按照以下步骤操作:

  1. http://www.sharedcache.com/releases 下载安装程序包,并将其安装到您的系统上。
  2. 打开或创建您想实现此缓存的应用程序。
  3. 添加对 MergeSystem.Indexus.WinServiceCommon.dll 的引用,该文件位于共享缓存的安装目录中。
  4. 将配置文件添加到应用程序中(稍后讨论)。
  5. 如果您想在缓存中存储自定义对象(例如,实体或 POCO),则使其可序列化。所有值类型都可以轻松缓存。例如:以下代码创建了一个可以缓存的 Person 类。
  6. /// <summary>
    /// A custom object that represent a person entity and can be store into cache
    /// </summary>
    [Serializable()]
    class Person
    {
        private string _fName;
        private string _lName;
        private int _age;
    
        public Person()
        {
            this._fName = string.Empty;
            this._lName = string.Empty;
            this._age = 0;
        }
    
        public Person(string firstName, string lastName, int age)
        {
            this._fName = firstName;
            this._lName = lastName;
            this._age = age;
        }
    
        public string FirstName
        {
            get { return _fName; }
            set { _fName = value; }
        }
    
        public string LastName
        {
            get { return _lName; }
            set { _lName = value; }
        }
    
        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
    
        public override string ToString()
        {
            return string.Format("First Name : {0},Last Name : {1}, Age : {2}", 
            _fName, _lName, _age);
        }
    
    }
  7. 现在,实现一个缓存包装器实用工具类,用于调用 SharedCache 上的缓存函数。虽然这不是强制性的,但您可以直接调用这些函数。
  8. /// <summary>
    /// A wrapper class to provide cache manipulation
    /// </summary>
    class CacheUtil
    {
        /// <summary>
        /// Add an object to cache with specified key
        /// </summary>
        /// <param name="key">A key value to asscociate with given object</param>
        /// <param name="obj">An object to store into cache</param>
        public static void Add(string key, object obj)
        {
            IndexusDistributionCache.SharedCache.Add(key, obj);
        }
        /// <summary>
        /// Retrieve an object from cache using given key value
        /// </summary>
        /// <param name="key">A key value to retrieve object from hash</param>
        /// <returns>An object that is associated with given key value</returns>
        public static object Get(string key)
        {
            return IndexusDistributionCache.SharedCache.Get(key);
        }
    
        /// <summary>
        /// Remove an object from cache using given key value
        /// </summary>
        /// <param name="key">A key value to remove item from cache</param>
        /// <returns>Result of remove operation true or false</returns>
        public static bool Remove(string key)
        {
            return IndexusDistributionCache.SharedCache.Remove(key);
        }
        /// <summary>
        /// List all objects present in cache
        /// </summary>
        /// <param name="serverName">Name of server for retrieving cache items</param>
        /// <returns>List of objects from cache</returns>
        public static List<Object> GetObjectList(string serverName)
        {
            List<Object> objs = new List<object>();
            foreach  (string k in IndexusDistributionCache.SharedCache.GetAllKeys(
                serverName))
            {
                objs.Add(IndexusDistributionCache.SharedCache.Get(k));
            }
            return objs;
        }
    }
  9. 太棒了!现在我们准备好缓存对象了。以下代码创建了一个基于菜单的控制台应用程序,该应用程序演示了缓存对象、从缓存中删除对象以及从缓存中检索对象的几种方法。
  10. 简而言之,我们可以使用以下函数来检索/添加/删除对象到/从缓存中。

    • CacheUtil.Add - 将对象添加到缓存中。
    • CacheUtil.Get - 从缓存中检索对象。
    • CacheUtil.Remove - 从缓存中删除对象。
    • CacheUtil.GetObjectList - 检索缓存中存储的所有对象。
    class Program
    {
        static void Main(string[] args)
        {
            Person dummyObject = new Person("Test", "User", 55);
            char myChoice = PrintMenu();
            while (myChoice != 'E')
            {
                string key;
                switch (myChoice)
                {
                    case 'A':
                        key = ReadKeyValue();
                        dummyObject = ReadPersonDetail();
                        CacheUtil.Add(key, dummyObject);
                        Console.WriteLine();
                        Console.WriteLine("Object Added with key : " + key);
                        DisplayObjects();
                        break;
                    case 'B':
                        key = ReadKeyValue();
                        dummyObject = (Person)CacheUtil.Get(key);
                        Console.WriteLine();
                        Console.WriteLine("Object retrieved with key : " + key);
                        Console.WriteLine(dummyObject.ToString());
                        break;
                    case 'C':
                        key = ReadKeyValue();
                        CacheUtil.Remove(key);
                        Console.WriteLine();
                        Console.WriteLine("Object deleted from cache with key : " + key);
                        DisplayObjects();
                        break;
                    case 'D':
                        DisplayObjects();
                        break;
                }
                myChoice = PrintMenu();
            }
        }
    
        static char PrintMenu()
        {
            Console.WriteLine();
            Console.WriteLine("*************************************");
            Console.WriteLine("A : Add an item into cache");
            Console.WriteLine("B : Retrieve item from cache");
            Console.WriteLine("C : Delete item from cache");
            Console.WriteLine("D : List items in cache");
            Console.WriteLine("E : Quit");
            Console.WriteLine("*************************************");
            Console.WriteLine("Enter [A,B,C,D] :");
    
            char ch= Console.ReadKey(false).KeyChar;
            while (!(ch >= 'A' && ch < 'F'))
            {
                Console.WriteLine();
                Console.WriteLine("Invalid choice : " + ch);
                Console.WriteLine("Enter [A,B,C,D] :");
                ch=Console.ReadKey(false).KeyChar;
            }
            return ch;
        }
    
        static string ReadKeyValue()
        {
            Console.WriteLine();
            Console.Write("Enter Key value :");
            return Console.ReadLine();
        }
    
        static Person ReadPersonDetail()
        {
            Person newPerson = new Person();
            Console.WriteLine();
            Console.WriteLine("Enter First Name : ");
            newPerson.FirstName = Console.ReadLine();
            Console.WriteLine("Enter Last Name : ");
            newPerson.LastName = Console.ReadLine();
            Console.WriteLine("Enter Age : ");
            char age = Console.ReadKey(false).KeyChar;
            while (! char.IsNumber(age))
            {
                Console.WriteLine("Invalid, Enter again : ");
                age = Console.ReadKey(false).KeyChar;
            }
            newPerson.Age = int.Parse(age.ToString());
            return newPerson;
        }
    
        static void DisplayObjects()
        {
            List<Object> objs=CacheUtil.GetObjectList("127.0.0.1");
            Console.WriteLine("Object's in cache ");
            Console.WriteLine("************************************");
            foreach (Object obj in objs)
            {
                Console.WriteLine(obj.ToString());
            }
        }
    }
  11. 要缓存对象,请在命令窗口中运行以下命令来启动缓存服务:
  12. c:\> net start IndeXus.Net

    (仅当您已在计算机上安装 SharedCache 时才会运行)。

如果启动成功,就可以享受 SharedCache 带来的缓存乐趣了。

演示应用程序的初始屏幕,显示菜单选项

scr1.png

显示已提交到 SharedCache 的条目的屏幕

scr2.png

显示 SharedCache 服务器运行状态的屏幕。

srvr.png

如何配置缓存

请按照以下步骤在您的应用程序中配置 SharedCache:

  1. configSections 中插入 NLog 和 SharedCache 的条目。这将允许在各自的配置节中配置 NLog 和 SharedCache。
  2. <configSections>
    
     <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
    
     <section name="NetSharedCache" 
      type="MergeSystem.Indexus.WinServiceCommon.Configuration.
            Client.IndexusProviderSection, MergeSystem.Indexus.WinServiceCommon"/>
    
    </configSections>
  3. 接下来,在 appSettings 中添加一些 SharedCache 特定的条目。这些设置将在运行时由 SharedCache 读取,并控制 SharedCache 的行为。
  4. <appSettings>
        <!--Version Number of SharedCache you are using as reference in
            application -->
        <add key="SharedCacheVersionNumber" value="2.0.0.140"/>
        
        <!--This indicates whether events should be logged while caching objects by  
            SharedCache-->
    
        <add key="LoggingEnable" value="1"/>
        
        <!--This setting points to server where cache service is running-->
        <add key="ServiceCacheIpAddress" value="127.0.0.1"/>
        
        <!--This is the port number on which Cache Service is listening of cache 
        intercepts-->
    
        <add key="ServiceCacheIpPort" value="48888"/>
        
        
        <!--
            Enable or disable compression upon client before transfer starts to server.
                Enable: 1
                Disable: 0            
        -->
        <add key="CompressionEnabled" value="0"/>
        
        <!--
            This sets the minimum size required for compressing object in Bytes.
            e.g.
              1kb = 1024
             10kb = 10240
            If this set to 0, every single payload will be compressed
            If this set to negative numbers the compression will not work.
        -->
        <add key="CompressionMinSize" value="10240"/>
        
    </appSettings>
  5. 现在,这是 SharedCache 的配置部分:
  6. <NetSharedCache defaultProvider="IndexusSharedCacheProvider">
        <!-- This includes list of server's where cache service is running -->
        <servers>
            <add key="localhost" 
              ipaddress="127.0.0.1" port="48888"/>
        </servers>
        
        <!-- This is list of Cache Provider,By default it is 
        IndexusSharedCacheProvider -->
        
        <providers>
            <add name="IndexusSharedCacheProvider" 
    
            type="MergeSystem.Indexus.WinServiceCommon.Provider.Cache.
            IndexusSharedCacheProvider, MergeSystem.Indexus.WinServiceCommon">
            </add>
        </providers>
    </NetSharedCache>
  7. 下一节专门用于配置日志记录功能。如果您通过将 LoggingEnable 设置为 '1' 来启用日志记录,则以下节将配置日志记录。SharedCache 生成四种日志:常规、流量、跟踪和同步日志。以下节配置所有类型的日志记录:
  8. <nlog autoReload="true" throwExceptions="true">
        <targets async="true">
            <target name="shared_cache_general" type="File" 
              layout="${longdate}|${level:uppercase=true}|${message}" 
              filename="${basedir}/logs/${date:format=yyyy-MM-dd}_
                        shared_cache_general_log.txt"/>
    
           <target name="shared_cache_traffic" type="File" 
              layout="${longdate}|${level:uppercase=true}|${message}" 
              filename="${basedir}/logs/${date:format=yyyy-MM-dd}_
                        shared_cache_traffic_log.txt"/>
    
          <target name="shared_cache_tracking" type="File" 
            layout="${longdate}|${level:uppercase=true}|${message}" 
            filename="${basedir}/logs/${date:format=yyyy-MM-dd}_
                      shared_cache_tracking_log.txt"/>
    
          <target name="shared_cache_sync" type="File" 
            layout="${longdate}|${level:uppercase=true}|${message}" 
            filename="${basedir}/logs/${date:format=yyyy-MM-dd}_
                      shared_cache_sync_log.txt"/>
      </targets>
      
      <rules>
            <logger name="General" minlevel="Debug" 
                writeTo="shared_cache_general" final="true"/>
            <logger name="Traffic" minlevel="Debug"
                writeTo="shared_cache_traffic" final="true"/>
            <logger name="Tracking" minlevel="Debug" 
                writeTo="shared_cache_tracking" final="true"/>
            <logger name="Sync" minlevel="Debug" 
                writeTo="shared_cache_sync" final="true"/>
            <logger name="*" minlevel="Debug" 
                writeTo="shared_cache_general"/>
            <logger name="*" minlevel="Info" 
                writeTo="shared_cache_general"/>
        </rules>
    </nlog>

在本文中,我描述了如何使用 SharedCache 配置高级缓存。希望这能为读者提供一些见解。

欢迎随时提出您的建议和疑问 :)

致谢

我想将本文的功劳归于 SharedCache 和 Abhijeet。

  1. Shared Cache - 感谢其出色的开源缓存框架和图片。
  2. Abhijeet Jana - 感谢他精彩的文章:《探索 ASP.NET 中的缓存》。

免责声明

此实用程序不遵循任何标准设计模式,由作者“按原样”提供,并且不提供任何明示或暗示的保证,包括但不限于对适销性和特定用途适用性的默示保证。在任何情况下,作者均不对因使用本软件而引起的任何直接、间接、偶然、特殊、惩戒性或后果性损害(包括但不限于购买替代商品或服务;使用、数据或利润损失;或业务中断)负责,无论其原因如何,也无论其责任理论如何,包括合同、严格责任或侵权(包括疏忽或其他)的责任,即使已被告知发生此类损害的可能性。所有图像、徽标和引用均属于其各自所有者的版权

历史

  • 初始修订:1.0。
© . All rights reserved.