ASP.NET缓存解决方案和最佳实践

 
1、概述
在ASP.NET应用程序构建过程中,为了提高应用程序的性能,缓存处理无疑是一个非常重要的环节。通常,我们将一些频繁被访问的数据,以及一些需要大量处理时间得出来的数据缓存在内存中,从而提高性能。例如,如果程序需要处理一张报表,这张报表的数据是关联的几张数据库表,并通过大量的计算得到的数据。我们知道表关联是比较耗时的,如果关联之后得出的数据再进行聚合排序等操作的话,那速度会更慢。因此,我们把查询的报表数据缓存起来,等下次用户再次请求时直接从内存中读取已经生成好的报表,这样对用户和程序无疑都是一件非常好的事情,用户减少了等待时间,程序减轻了压力。
  
那么,何乐而不为呢,既然能让大家都开心的事情我们就去做吧。为此,ASP.NET提供了两种缓存方案。第一种是页输出缓存,它保存页处理输出,并在用户再次请求该页时,重用所保存的输出,而不是再次处理该页。第二种是应用程序缓存,它允许缓存您生成的数据,比如自定义报表对象,DataSet,DataTable等。但是有个问题就是ASP.NET为我们提供的缓存方案只能应用在单服务器中,如果我们的应用程序有几台服务器做负载均衡,或者我们做分布式应用,那么,ASP.NET为我们提供的缓存解决方案发挥的作用就不大了,我们需要其他的解决方案,现在比较成熟的缓存框架有Memcached,此框架用于分布式系统中,适用于Java,ASP.NET,PHP,Ruby等语言环境构建的应用程序。
  
那么,下面就一一阐述以上提到的缓存方案。
  
2、页输出缓存
在页输出缓存中,ASP.NET为我们提供了两种解决方案,第一种是页面级输出缓存,第二种是片段缓存(用户控件缓存)。两种方案各有各的应用场景,我们来分别阐述。
  
2.1、页面级输出缓存
页面级输出缓存是比较简单的缓存形式,它是将响应请求而发送的HTML副本保存在内存中,当再有请求时直接将缓存的HTML输出给用户,直到缓存过期。这样,程序的性能会得到非常大的提升。
  
实现
  
具体的实现就非常简单了,只要页面顶部加一条OutputCache指令就可以了。
  
<%@ OutputCache Duration="10" VaryByParam="none" %>
  
它支持五个属性(Duration,VaryByParam,Location,VaryByCustom,VaryByHeader),有两个(Duration,VaryByParam)是必须的,我们也就研究这两个属性就可以了,也基本够我们日常使用。
  
l Duration:页面应该被缓存的时间,以秒为单位。必须是正整数。
  
l VaryByParam :Request 中变量的名称,这些变量名应该产生单独的缓存条目。"none" 表示没有变动。"*" 可用于为每个不同的变量数组创建新的缓存条目。变量之间用 ";" 进行分隔。
  
l Location :指定应该对输出进行缓存的位置。如果要指定该参数,则必须是下列选项之一:Any、Client、Downstream、None、Server 或 ServerAndClient。
  
l VaryByHeader :基于指定的标头中的变动改变缓存条目。
  
l VaryByCustom :允许在 global.asax 中指定自定义变动(例如,"Browser")。
  
示例
  
<1>.在Visual Studio .NET新建一个web项目,并且新建一个.aspx页面
  
<2>.删除页面的上面的默认HTML代码
  
<3>.把下面的代码COPY到刚新建的那个页面中
  
<%@ OutputCache Duration="10" VaryByParam="none"%>
  
<html>
  
<head runat="server">
  
<title>页面输出缓存示例</title>
  
<script type="text/C#" runat="server">
  
void Page_Load(object sender, EventArgs e)
  
{
  
this.lblTime.Text = "Time:" + DateTime.Now.ToString();
  
}
  
</script>
  
</head>
  
<body>
  
<strong>页面输出缓存示例</strong>
  
<hr />
  
<br />
  
<asp:Label ID="lblTime" runat="server"></asp:Label>
  
<br />
  
<hr />
  
<a href="opc.aspx?categoryid=test1">categoryid(test1)</a>
  
<br />
  
<a href="opc.aspx?categoryid=test2">categoryid(test1)</a>
  
</body>
  
</html>
  
<4>.在浏览器中浏览此页面,您会页面上面的Time会有10秒的缓存,每过10秒,Time会变化一次,这时就是Duration="10"属性在起作用,因为我设置了缓存时间为10秒。好的,我们已经测试了Duration="10"属性。
  
<5>.我们点击下面的categoryid(test1)和categoryid(test2)两个链接,发现Time是一样的,为什么呢?那是因为我们设置VaryByParam属性为none,我们之前解释过VaryByParam属性为none表示没有变动,意为保存一个缓存,适用于页面只有一个缓存的情况。那我们现在这样一个情况,有一个产品列表数据,其数据是根据产品的分类来决定显示哪些产品,所以我们这里的关键问题是为每个分类产品分别产生缓存。这时就需要用到VaryByParam属性了,它的用途我们已经知道了,现在我们把它的属性设置为categoryid,现在再试试分别点击两个链接,你就会看到两个链接的页面缓存不一样了。
  
实战友情提示:
  
l 切记,Duration 是用秒进行指定的。
  
l 在使用 VaryByParam时,一定要注意 Request 变量大小写的变化会导致额外的缓存。比如刚才示例中categoryid=test1和categoryid=Test1会产生两个缓存版本,这里应用时要注意。
  
2.2、片段缓存(用户控件缓存)
对于页面级输出缓存的整页缓存方案,片段缓存是把页面的某个部分进行缓存,缓存一些很多页面所共有的页面部分,这样更节约内存资源,节约服务器压力,更符合面向对象的特点(封装)。比如页面的页头和页尾,很多页面都是公用相同的页头和页尾。再比如菜单部分,很多页面也是公用的一个菜单。这样,我们就可以一处缓存,多处使用。 像这样类似的场景我们就可以用片段缓存来实现。
  
实现
  
片段缓存的使用语法和页面输出缓存基本一样,但其应用于用户控件(.ascx文件),而页面输出缓存是应用于页面(.aspx文件)。对于其属性,它支持页面级输出缓存(除了Location属性)所有属性。并且用户控件还支持VaryByControl属性,该属性将根据用户控件(通常为用户控件页面上的控件,比如dropdownlist)成员的值改变而改变该控件的缓存。如果指定了VaryByControl,可以省略VaryByParam。
  
在默认情况下,对每一个页面上面引用的每个用户控件都是单独缓存的。如果一个用户控件不随应用程序中的页面改变而改变,并且在所有的页面中使用相同的名称(ID相同),且使用了Shared="true"参数,那么所有引用该用户控件的缓存版本都是一样的。
  
示例
  
<1>. 借用页面级输出缓存建立的WEB项目,新建一个用户控件(.ascx文件)
  
<2>. 把以下代码COPY到刚才新建的.ascx的页面文件中
  
<%@ OutputCache Duration="10" VaryByControl="ddlcity" Shared="true" %>
  
<script type="text/C#" runat="server">
  
protected void Page_Load(object sender, EventArgs e)
  
{
  
this.lblUCTime.Text = "usercontrol time:"+ DateTime.Now.ToString();
  
}
  
</script>
  
<asp:DropDownList ID="ddlcity" runat="server" AutoPostBack="True">
  
<asp:ListItem Value="1">北京</asp:ListItem>
  
<asp:ListItem Value="2">江苏</asp:ListItem>
  
<asp:ListItem Value="3">上海</asp:ListItem>
  
<asp:ListItem Value="3">南京</asp:ListItem>
  
</asp:DropDownList>
  
<br />
  
<hr />
  
<br />
  
<asp:Label ID="lblUCTime" runat="server"></asp:Label>
  
<br />
  
<3>. 新建一个.aspx页面,并删除页面文件中的HTML代码,把以下代码复制到页面文件中(注意顶部.cs文件引用别删除,有下划线的需要您替换)
  
<%@ Register Src="您的用户控件.ascx" TagName="ucc" TagPrefix="uc1" %>
  
<html >
  
<head runat="server">
  
<title>片段缓存示例</title>
  
<script type="text/C#" runat="server">
  
protected void Page_Load(object sender, EventArgs e)
  
{
  
this.lblSelfTime.Text = "self time:" + DateTime.Now.ToString();
  
}
  
</script>
  
</head>
  
<body>
  
<form id="form1" runat="server">
  
<strong>片段缓存示例</strong>
  
<br />
  
<hr />
  
<uc1:ucc ID="Ucc1" runat="server" />
  
<br />
  
<hr />
  
<asp:Label ID="lblSelfTime" runat="server"></asp:Label>
  
</form>
  
</body>
  
</html>
  
<4>. 再新建一个.aspx文件,并删除页面文件中的HTML代码,把以下代码复制到页面文件中(注意顶部.cs文件引用别删除,有下划线的需要您替换)
  
<%@ Register Src="您的用户控件.ascx" TagName="ucc" TagPrefix="uc1" %>
  
<html >
  
<head id="Head1" runat="server">
  
<title>片段缓存示例2</title>
  
<script type="text/C#" runat="server">
  
protected void Page_Load(object sender, EventArgs e)
  
{
  
this.lblSelfTime.Text = "self 2 time:" + DateTime.Now.ToString();
  
}
  
</script>
  
</head>
  
<body>
  
<form id="form1" runat="server">
  
<strong>片段缓存示例2</strong>
  
<br />
  
<hr />
  
<uc1:ucc ID="Ucc1" runat="server" />
  
<br />
  
<hr />
  
<asp:Label ID="lblSelfTime" runat="server"></asp:Label>
  
</form>
  
</body>
  
</html>
  
<5>. 在浏览器中浏览您刚才新建的两个.aspx页面,会发现两个页面的用户控件的缓存是一样的(usercontrol time 是同时变化的),当您选择城市列表时,会发现用户控件为每一个城市都缓存了一个版本。不同页面的每个城市的缓存版本一样。
  
如果需要每个页面缓存版本不一样,就不要设置Shared="true"参数。大家可以通过上面的示例自己测试测试。
  
实战友情提示:
  
l 如果想每个页面引用的用户控件的缓存版本一样,就必须设置Shared="true"参数,并且用户控件ID一样
  
3、缓存后替换
与控件缓存正好相反。它对整个页面进行缓存,但是页中的某些片段是动态的,因此不会缓存这些片段。ASP.NET页面中既包含静态内容,又包含基于数据库数据的动态内容。静态内容通常不会发生变化。因此,对静态内容实现数据缓存是非常必要的。然而,那些基于数据的动态内容,则不同。数据库中的数据可能每时每刻都发生变化,因此,如果对动态内容也实现缓存,可能造成数据不能及时更新的问题。对此问题如果使用前文所述的控件缓存方法,显然不切实际,而且实现起来很繁琐,易于发生错误。
     如何实现缓存页面的大部分内容,而不缓存页面中的局部某些片段。ASP.NET 2.0提供了缓存后替换功能。实现该项功能可通过以下三种方法:
    一是以声明方式使用Substitution控件,
    二是以编程方式使用Substitution控件API,
    三是以隐式方式使用控件。
    前两种方法的核心是Substitution控件,本节将重点介绍该控件,第三种方法仅专注于控件内置支持的缓存后替换功能,本节仅做简要说明。
(1)Substitution控件应用 
     为提高应用程序性能,可能会缓存整个ASP.NET页面,同时,可能需要根据每个请求来更新页面上特定的部分。例如,可能要缓存页面的很大一部分,需要动态更新该页上与时间或者用户高度相关的信息。在这种情况下,推荐使用Substitution控件。Substitution控件能够指定页面输出缓存中需要以动态内容替换该控件的部分,即允许对整页面进行输出缓存,然后,使用Substitution控件指定页中免于缓存的部分。需要缓存的区域只执行一次,然后从缓存读取,直至该缓存项到期或被清除。动态区域,也就是Substitution控件指定的部分,在每次请求页面时都执行。Substitution控件提供了一种缓存部分页面的简化解决方案。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="CacheWebApp._16_4_5.WebForm2" %>
<%@ OutputCache Duration="60" VaryByParam="none" %>
<head runat="server">
    <title>缓存后替换示例</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    页面缓存的时间:<%= DateTime.Now.ToString() %>   
    </div>
    <div>    
       真实(替换)的时间:<asp:Substitution ID="Substitution1" runat="server" MethodName="getCurrentTime" />
    </div>
    </form>
</body>
</html>
  
    页面后台代码:
public partial class WebForm2 : System.Web.UI.Page
{
    public static string getCurrentTime(HttpContext context)
    {
        return DateTime.Now.ToString();
    }
}
  
如上代码所示,Substitution控件有一个重要属性:MethodName。该属性用于获取或者设置当Substitution控件执行时为回调而调用的方法名称。该方法比较特殊,必须符合以下3条标准:
? 此方法必须被定义为静态方法;
? 此方法必须接受HttpContext类型的参数;
? 此方法必须返回String类型的值。
     在运行情况下,Substitution控件将自动调用MethodName属性所定义的方法。该方法返回的字符串即为要在页面中的Substitution控件的位置上显示的内容。如果页面设置了缓存全部输出,那么在第一次请求时,该页将运行并缓存其输出。对于后续的请求,将通过缓存来完成,该页上的其他代码不会再运行。但Substitution控件及其有关方法则在每次请求时都执行,并且自动更新该控件所表示的动态内容,这样就实现了整体缓存,局部变化的替换效果。
     如上代码所示,在代码头部通过@ OutputCache指令设置页面输出缓存过期时间为5秒,这意味着整个页面数据都应用了缓存功能。因此,“页面缓存的时间”所显示的时间值来自于数据缓存。这个时间值不会随着刷新页面而变化,仅当缓存过期时才会发生更新。Substitution控件的MethodName属性值为getCurrentTime。该控件显示的内容来自于getCurrentTime方法的返回值。尤为重要的是,虽然页面设置了输出缓存功能,但是每当页面刷新时,ASP.NET执行引擎仍然要重新执行Substitution控件,并将MethodName属性值指定的方法返回值显示在页面上,因此,显示的是当前最新时间。
示例效果,如图15-2所示:
图15-2 缓存后替换
随着页面的刷新,真实时间在变,而页面缓存的时间在指定的缓存时间内始终不变。
注意:
l Substitution控件无法访问页上的其他控件,也就是说,无法检查或更改其他控件的值。但是,代码确实可以使用传递给它的参数来访问当前页上下文。
l 在缓存页包含的用户控件中可以包含Substitution控件。但是,在输出缓存用户控件中不能放置Substitution控件。
l Substitution控件不会呈现任何标记,其位置所显示内容完全取决于所定义方法的返回字符串。
  
  
(2)Substitution控件API应用 
上一小节介绍了以声明方式使用Substitution控件实现缓存后替换的应用。本节说明另一种实现方法。该方法的核心是以编程方式利用Substitution控件API实现缓存后替换,相对于以声明方式使用Substitution控件的方法具有更强灵活性。
通过为Substitution指定回调方法,实现和声明同样的效果。Substitution的回调方法必须是
HttpResponseSubstitutionCallback委托定义的方法,它有两个特征:
l 一是返回值必须是String,
l 二是参数有且仅有一个,并且是HttpContext类型。
当需要以编程方式,为缓存的输出响应动态生成指定的响应区域时,可以在页面代码中将某个方法(即回调方法)的名称作为参数(HttpResponseSubstitutionCallback)传递给Substitution。这样Substitution就能够使用回调方法,并将回调方法的返回值作为给定位置的替代内容显示出来。
需要注意的是,回调方法必须是线程安全的,可以是作为容器的页面或者用户控件中的静态方法,也可以是其他任意对象上的静态方法或实例方法。
下面演示一个以编程方式将 Substitution 控件添加到输出缓存网页。与(1)Substitution控件应用所示的示例完成同样功能。不同的是实现方式。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm3.aspx.cs" Inherits="CacheWebApp._16_4_5.WebForm3" %>
<%@ OutputCache Duration="60" VaryByParam="none" %>
<head runat="server">
    <title>缓存后替换-Substitution控件API应用</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        页面缓存的时间:<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
    </div>
    <div>
        真实(缓存替换)的时间:
        <asp:PlaceHolder ID="PlaceHolder1" runat="Server"></asp:PlaceHolder>
    </div>
    </form>
</body>
</html>
    页面后台CS代码:   
protected void Page_Load(object sender, EventArgs e)
{
    //创建一个Substitution
    Substitution Substitution1 = new Substitution();
    //指定调用的回调方法名
    Substitution1.MethodName = "GetCurrentDateTime";            
    PlaceHolder1.Controls.Add(Substitution1);        
    Label1.Text=DateTime.Now.ToString();    
}
public static string GetCurrentDateTime(HttpContext context)
{
    return DateTime.Now.ToString();
}
如上代码所示,页面使用@ OutputCache指令设置了输出缓存功能,其配置数据缓存过期时间为60秒。然而,页面其他内容都被缓存,通过Substitution调用的回调方法显示的内容是不被缓存的。
  
  
4、应用程序缓存
页面级和用户控件级缓存的确是一种可以迅速而简便地提高站点性能的方法,但是在ASP.NET中,缓存的真正灵活性和强大功能是通过Cache (System.Web.Caching.Cache)对象提供的。使用 Cache对象,您可以存储任何可序列化的数据对象,基于一个或多个依赖项的组合来控制缓存条目到期的方式。这些依赖项可以包括自从项被缓存后经过的时间、自从项上次被访问后经过的时间、对文件和/或文件夹的更改以及对其他缓存项的更改,在略作处理后还可以包括对数据库中特定表的更改。
  
实现
  
Cache对象位于System.Web.Caching. Cache中,其提供了两种增加缓存的方法,Add()和Insert()方法,这两种方法都有多个重载,且两种方法唯一的区别就是Add()返回已缓存对象的引用,Insert()没有返回值。Cache对象还提供了删除缓存的Remove()方法。
  
具体的缓存实践我这里提供了简易封装后一个缓存工具类。可以直接用于项目中(适用于ASP.NET 2.0项目)。
  
using System;
  
using System.Text;
  
using System.Web.Caching;
  
using System.Collections;
  
using System.Collections.Generic;
  
using System.Text.RegularExpressions;
  
namespace DianPing001.Cache
  
{
  
public static class ObjectCache
  
{
  
private static System.Web.Caching.Cache cache;
  
private static double _SaveTime;
  
/// <summary>
  
/// 缓存保存时间,以分钟计算,默认分钟
  
/// </summary>
  
public static double SaveTime
  
{
  
get { return _SaveTime; }
  
set { _SaveTime = value; }
  
}
  
static ObjectCache()
  
{
  
cache = System.Web.HttpContext.Current.Cache;
  
_SaveTime = 30.0;
  
}
  
/// <summary>
  
/// 获取缓存对象
  
/// </summary>
  
/// <param name="key">key</param>
  
/// <returns>object</returns>
  
public static object Get(string key)
  
{
  
return cache.Get(key);
  
}
  
/// <summary>
  
/// 获取缓存数据,需要传入类型
  
/// </summary>
  
public static T Get<T>(string key)
  
{
  
object obj = Get(key);
  
if (obj == null)
  
{
  
return default(T);
  
}
  
else
  
{
  
return (T)obj;
  
}
  
}
  
/// <summary>
  
/// 插入对象到缓存中
  
/// </summary>
  
/// <param name="key">key</param>
  
/// <param name="value">对象</param>
  
/// <param name="dependency">对象依赖</param>
  
/// <param name="priority">优先级</param>
  
/// <param name="callback">缓存删除时的回调事件</param>
  
public static void Insert(string key, object value, CacheDependency dependency, CacheItemPriority priority, CacheItemRemovedCallback callback)
  
{
  
cache.Insert(key, value, dependency, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(SaveTime), priority, callback);
  
}
  
/// <summary>
  
/// 插入对象到缓存中
  
/// </summary>
  
/// <param name="key">key</param>
  
/// <param name="value">对象</param>
  
/// <param name="dependency">对象依赖</param>
  
/// <param name="callback">缓存删除时的回调事件</param>
  
public static void Insert(string key, object value, CacheDependency dependency, CacheItemRemovedCallback callback)
  
{
  
Insert(key, value, dependency, CacheItemPriority.Default, callback);
  
}
  
/// <summary>
  
/// 插入对象到缓存中
  
/// </summary>
  
/// <param name="key">key</param>
  
/// <param name="value">对象</param>
  
/// <param name="dependency">对象依赖</param>
  
public static void Insert(string key, object value, CacheDependency dependency)
  
{
  
Insert(key, value, dependency, CacheItemPriority.Default, null);
  
}
  
/// <summary>
  
/// 插入对象到缓存中
  
/// </summary>
  
/// <param name="key">key</param>
  
/// <param name="value">对象</param>
  
public static void Insert(string key, object value)
  
{
  
Insert(key, value, null, CacheItemPriority.Default, null);
  
}
  
/// <summary>
  
/// 获取所有缓存对象的key
  
/// </summary>
  
/// <returns>返回一个IList对象</returns>
  
public static IList<string> GetKeys()
  
{
  
List<string> keys = new List<string>();
  
IDictionaryEnumerator cacheItem = cache.GetEnumerator();
  
while (cacheItem.MoveNext())
  
{
  
keys.Add(cacheItem.Key.ToString());
  
}
  
return keys.AsReadOnly();
  
}
  
/// <summary>
  
/// 删除缓存对象
  
/// </summary>
  
/// <param name="key">key</param>
  
public static void Remove(string key)
  
{
  
cache.Remove(key);
  
}
  
/// <summary>
  
/// 删除全部缓存
  
/// </summary>
  
public static void RemoveAll()
  
{
  
IList<string> keys = GetKeys();
  
foreach (string key in keys)
  
{
  
cache.Remove(key);
  
}
  
}
  
public static IList<string> RegexSearch(string pattern)
  
{
  
List<string> keys = new List<string>();
  
IDictionaryEnumerator cacheItem = cache.GetEnumerator();
  
while (cacheItem.MoveNext())
  
{
  
if (Regex.IsMatch(cacheItem.Key.ToString(), pattern))
  
{
  
keys.Add(cacheItem.Key.ToString());
  
}
  
}
  
return keys.AsReadOnly();
  
}
  
/// <summary>
  
/// 删除符合正则条件的cache
  
/// </summary>
  
/// <param name="pattern">条件</param>
  
public static void RegexRemove(string pattern)
  
{
  
IList<string> keys = RegexSearch(pattern);
  
foreach (string key in keys)
  
{
  
cache.Remove(key);
  
}
  
}
  
}
  
}
  
具体的使用场景
  
l 添加缓存
  
/// <summary>
  
/// 获取全部友情链接
  
/// </summary>
  
/// <returns></returns>
  
public List<Links> GetAll()
  
{
  
List<Links> linksList = ObjectCache.Get<List<Links>>("c_Links_ALL"); [stone1]
  
if (linksList == null)
  
{
  
linksList = ProviderManager.Factory.Links.GetAll();
  
ObjectCache.Insert("c_Links_ALL",linksList);
  
}
  
[stone2] return linksList;
  
}
  
l 删除缓存
  
/// <summary>
  
/// 删除友情链接
  
/// </summary>
  
/// <param name="id"></param>
  
/// <returns></returns>
  
public int Delete(int id)
  
{
  
int retVar = ProviderManager.Factory.Links.Delete(id);
  
if (retVar > 0)
  
{
  
ObjectCache.RegexRemove("c_Links*");[stone3]
  
}
  
return retVar;
  
}
  
到这里,已经介绍了ASP.NET为我们提供的缓存方案,从简单的页面级和用户控件缓存,到功能强大、可灵活定制的Cache对象。这些已经基本满足我们日常的需求。当然,缓存的强大之处还需要我们在实战中慢慢体会。
  
4、分布式缓存
在ASP.NET中已经为我们提供了一些缓存方案,但是如果我们需要搭建分布式缓存系统的话,ASP.NET提供的方案就不够用了。因此我们需要其他的解决方案。寻觅的一段时间后,发现一个叫Memcached的用于分布式系统的缓存方案。如果你想快速搭建性能卓越,功能强大的分布式系统,那Memcached绝对是您不二的选择。
  
1) Memcached是什么?
  
memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件。许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、网站显示延迟等重大影响。
  
这时就该memcached大显身手了。memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。
  
2) Memcached能缓存什么?
  
通过在内存里维护一个统一的巨大的hash表,Memcached能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。
  
3) Memcached快吗?
  
非常快,必须要介绍它的内部实现原理,只要知道有哪些站点在应用就可以了。Memcached已经成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。所以我们应该有理由相信Memcached的性能。
  
4) Memcached特点
  
memcached作为高速运行的分布式缓存服务器,具有以下的特点。
  
? 协议简单
  
? 基于libevent的事件处理
  
? 内置内存存储方式
  
? memcached不互相通信的分布式
  
5) Windows下Memcached的安装使用
  
a) 安装Memcached Server
  
u 下载memcached的windows稳定版,解压放某个盘下面,比如在d:\memcached
  
u 在CMD下输入 "d:\memcached\memcached.exe -d install" 安装.
  
u 再输入:"d:\memcached\memcached.exe -d start" 启动。
  
备注:以后memcached将作为windows的一个服务每次开机时自动启动。这样服务器端已经安装完毕了。有几台缓存机器就为这些机器分别安装Memcached服务。
  
安装常用设置:
  
-p <num> 监听的端口
  
-l <ip_addr> 连接的IP地址, 默认是本机
  
-d start 启动memcached服务
  
-d restart 重起memcached服务
  
-d stop|shutdown 关闭正在运行的memcached服务
  
-d install 安装memcached服务
  
-d uninstall 卸载memcached服务
  
-u <username> 以<username>的身份运行 (仅在以root运行的时候有效)
  
-m <num> 最大内存使用,单位MB。默认64MB
  
-M 内存耗尽时返回错误,而不是删除项
  
-c <num> 最大同时连接数,默认是1024
  
-f <factor> 块大小增长因子,默认是1.25
  
-n <bytes> 最小分配空间,key+value+flags默认是48
  
-h 显示帮助
  
b) 使用Memcached 的.NET客户端
  
u 下载Memcached的.NET客户端(C#)
  
u 在项目中引用Enyim.Caching.dll文件
  
u 添加配置文件,WEB项目为web.config,客服端软件项目为App.config,配置代码为
  
<configuration>
  
<configSections>
  
<sectionGroup name="enyim.com">
  
<section name="memcached" type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching" />
  
</sectionGroup>
  
<section name="memcached" type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching" />
  
</configSections>
  
<enyim.com>
  
<memcached>
  
<servers>
  
<!-- put your own server(s) here-->
  
<add address="127.0.0.1" port="11211" />
  
<add address="192.168.111.189" port="11212" />
  
</servers>
  
<socketPool minPoolSize="10" maxPoolSize="100" connectionTimeout="00:00:10" deadTimeout="00:02:00" />
  
</memcached>
  
</enyim.com>
  
<memcached keyTransformer="Enyim.Caching.TigerHashTransformer, Enyim.Caching">
  
<servers>
  
<add address="127.0.0.1" port="11211" />
  
<add address="192.168.111.189" port="11212" />
  
</servers>
  
<socketPool minPoolSize="2" maxPoolSize="100" connectionTimeout="00:00:10" deadTimeout="00:02:00" />
  
</memcached>
  
</configuration>
  
u 测试代码
  
using System;
  
using System.Collections.Generic;
  
using System.Text;
  
using Enyim.Caching;
  
using Enyim.Caching.Memcached;
  
using Enyim.Caching.Configuration;
  
using System.Net;
  
namespace TestMemcached1
  
{
  
class Program
  
{
  
static void Main(string[] args)
  
{
  
string flag = "1"; //输入TAG,为插入缓存,为读取缓存数据,为删除缓存数据
  
string key = "";
  
string value = "";
  
MemcachedClient mc = new MemcachedClient();
  
Console.WriteLine("请输入操作类型(1插入缓存,读取缓存数据,删除缓存数据)...");
  
while((flag = Console.ReadLine().Trim()) != "")
  
{
  
switch (flag)
  
{
  
case "1": {
  
Console.WriteLine("请输入要插入缓存的KEY:");
  
key = Console.ReadLine().Trim();
  
Console.WriteLine("请输入与KEY对应的值:");
  
value = Console.ReadLine().Trim();
  
if (mc.Store(StoreMode.Set, key, value))
  
{
  
Console.WriteLine("{0}的值({1})插入成功",key,value);
  
}
  
}; break;
  
case "2": {
  
Console.WriteLine("请输入要删除的缓存的KEY");
  
key = Console.ReadLine().Trim();
  
if (mc.Get(key) == null)
  
{
  
Console.WriteLine("SORRY,{0}的值不存在", key);
  
}
  
else
  
{
  
if (mc.Remove(key))
  
{
  
Console.WriteLine("删除缓存({0})成功", key);
  
}
  
else
  
{
  
Console.WriteLine("删除缓存({0})失败", key);
  
}
  
}
  
}; break;
  
case "0": {
  
Console.WriteLine("请输入要读取缓存数据的KEY");
  
key = Console.ReadLine().Trim();
  
if (mc.Get(key) == null)
  
{
  
Console.WriteLine("SORRY,{0}的值不存在", key);
  
}
  
else
  
{
  
Console.WriteLine("{0}的缓存数据为{1}",key,mc.Get(key));
  
}
  
}; break;
  
default: Console.WriteLine("谢谢使用"); break;
  
}
  
}
  
}
  
}
  
}
  
c) 运行结果
  
我是配置了两台服务器,本机和局域网内的一台机器,从配置文件中也可以看出具体配置了几台机器。
  
其运行结果也是非常让人满意的,我在本机添加的缓存,在192.168.111.189那台机器上面可以查询到刚刚添加的缓存,同样在189机器添加的缓存,我本机同样可以查询,当然删除也是同步的。
  
d) 查看Memcached运行情况
  
使用telnet IP 端口 然后使用stats命令查看Memcached运行情况
  
e) 实战友情提示:
  
Memcached在修改服务端口时发现CMD下的修改命令并不起效果。后来发现在安装好的Memcached服务的启动项中并没有端口设置(默认值为11211),于是想到进注册表修改其服务启动参数。打开注册表,按照路径HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\memcached Server,找到其中的ImagePath项,其值为:"d:\memcached\memcached.exe" -d runservice,将值修改为:"d:\memcached\memcached.exe" -p 80080 -m 256 -d runservice。重启服务后你就会发现该服务的端口已经变为80080了,-m 256表示设置了256M内存。
  
5、小结
到此为止,我们已经阐述了ASP.NET中各种缓存方案,并分享了一些实战经验。
  
从最基本的页面级和用户控件级简单缓存,到高灵活性、高性能的Cache缓存对象,以及功能强大、性能卓越的分布式缓存系统Memcached,我们都已经有所了解、有所深入。那么在实践过程中,我们应该根据自己的实际需要去选择具体的缓存方案。比如单服务器中,我们可以选择Cached缓存对象实现缓存,一些长期不做修改的动态页面我们可以选择页面级缓存,多页面公用的菜单我们可以选择用户控件缓存。分布式系统我们就可以选择Memcached解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值