19.6 分布式缓存Velocity
在早期的应用程序开发中,从磁盘或数据库中反复检索数据一直以来是应用程序的一个瓶颈问题,处理起来很是棘手。而在.NET Framework 4中,微软推出了代号为“Velocity”((Mcrosoft Distributed Caching Service)的分布式缓存解决方案,它提供了一种方法用来在内存型缓存中存储数据,以供将来检索,从而消除了对从磁盘或数据存储器获取数据的需求。同时,它为开发可扩展性、可用的、高性能的应用程提供了强有力的支持,它可以缓存各种类型的数据,如CLR对象、XML、二进制数据等,并且支持集群模式的缓存服务器。值得庆幸的是,Velocity被集成在.NET Framework 4中。
19.6.1 安装与操作Velocity
Velocity中提供了一套基于Windows PowerShell的管理工具,因此在安装Velocity之前,必须先安装Windows PowerShell。
接下来,就可以去微软的网站下载Velocity的安装包MicrosoftDistributedCache-i386.msi(http://www.microsoft.com/downloads/details.aspx?FamilyId=B24C3708-EEFF-4055-A867-19B5851E7CD2&displaylang=en)。
除此之外,在安装Velocity之前,还需要创建一个共享文件夹,这里命名为VelocityCache(即文件夹共享为\mawei-2ee0c5b1a\VelocityCache。在这里,需要将mawei-2ee0c5b1a更改为本地机器的网络名称)。当然,可以根据自己的需要来命名该共享文件夹。
现在就可以运行MicrosoftDistributedCache-i386.msi文件,将出现图19-8所示的欢迎安装窗体,单击“Next”按钮。
出现许可协议窗体,如图19-9所示。如果接受许可证协议,请选择“I accept the terms in the license agreement”单选按钮,单击“Next”按钮。
图 19-8 欢迎安装屏幕
图 19-9 许可协议
将出现“Ready To Install”窗体,如图19-10所示。可以单击Browse按钮选择安装路径,或者接受默认的安装位置(C:\Program Files\Microsoft Distributed Cache),并单击“Install”按钮。
图 19-10 “Ready To Install”窗体
在安装过程中,将提示你允许以下应用程序通过防火墙,如图19-11所示。单击“OK”按钮确认此操作。
安装之后,将出现Cache Host Configuration窗体,如图19-12所示。在该窗体中,需要配置以下参数:
1)对于Storage位置类型,选择“Shared network folder”选项。
2)对于Network path,键入前面所建立的共享文件夹,即“\mawei-2ee0c5b1a\VelocityCache”。
3)设置Service port number、Cluster port number和Max Cached Data Size,若无特别要求,这里建议使用默认值。
图 19-11 允许应用程序通过防火墙
图 19-12 Cache Host Configuration窗体
要创建群集,请单击图19-12中的“Test”按钮。出现确认对话框提示后,如图19-13所示,单击“是”按钮,从而创建群集。
图 19-13 确认对话框
创建群集后,将启用所有剩余控件,以继续配置Cache Host,如图19-14所示。需要设置以下参数以完成配置过程:
1)在Cluster Name处,键入相关名称,这里命名为“MyCluster”。
2)在Cluster Size处,选择Small(1-4)单选按钮,并单击“Save&Close”按钮。
出现一个完成窗体,如图19-15所示,单击“Finish”按钮完成整个安装过程。
图 19-14 Cache Host Configuration窗体
图 19-15 完成窗体
Velocity安装完成之后,就可以使用“Administration Tool-Microsoft Distributed Cache”工具来对Velocity缓存集群、缓存宿主等进行管理(该工具打开方法:程序|Microsoft Distributed Cache|Administration Tool-Microsoft Distributed Cache)。
例如,可以使用下面的命令来启动、停止、重启服务器集群:
Start-CacheCluster
Stop-CacheCluster
Restart-CacheCluster
其他详细命令,可以通过帮助命令((Gt-CacheHelp)来进行查看,如图19-16所示。
例如,可以使用Get-CacheHost命令来获取缓存宿主的信息,如图19-17所示。这样,就可以看到宿主对应的服务名以及服务状态等信息。
图 19-16 Get-CacheHelp命令
图 19-17 Get-CacheHost命令
19.6.2 存储与检索简单的数据
要在应用程序中使用Velocity,首先需要把CacheBaseLibrary.dll和ClientLibrary.dll这两个程序集引用到应用程序中。它们在Velocity安装目录下可以找到,如图19-18所示。
图 19-18 添加引用
下面将通过一个示例来演示如何使用Velocity缓存来存储与检索简单的数据。在页面代码中,定义了四个操作按钮,分别演示缓存的四种操作,即Add、Put、Get与Remove操作。如下面的代码所示:
<form id="form1"runat="server">
<div>
<table>
<tr>
<td>
<label>
Key:</label>
</td>
<td>
<asp:TextBox runat="server"ID="keyTextbox"/>
</td>
</tr>
<tr>
<td>
<label>
Value:</label>
</td>
<td>
<asp:TextBox runat="server"ID="valueTxtBox"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:Button runat="server"ID="bt_Add"
Text="Add"OnClick="bt_Add_Click"/>
<asp:Button runat="server"ID="bt_Put"
Text="Put"OnClick="bt_Put_Click"/>
<asp:Button runat="server"ID="bt_Get"
Text="Get"OnClick="bt_Get_Click"/>
<asp:Button runat="server"ID="bt_Remove"
Text="Remove"OnClick="bt_Remove_Click"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:Label runat="server"ID="statusLabel"/>
</td>
</tr>
</table>
</div>
</form>
要使用Velocity缓存,除了上面把CacheBaseLibrary.dll和ClientLibrary.dll程序集引用到应用程序中之外,还需要在代码文件里添加一个using,即
using Microsoft.Data.Caching;
现在,就可以通过创建一个命名缓存来缓存数据,它可以通过DataCacheFactory来创建。之后,在Session中存储缓存该对象,并在每次出现页面事件时检索该对象的相同实例。如下面的代码所示:
private DataCache GetCurrentCache()
{
DataCache dCache;
if(Session["dCache"]!=null)
{
dCache=((DtaCache)Session["dCache"];
}
else
{
var factory=new DataCacheFactory();
dCache=factory.GetCache("default");
Session["dCache"]=dCache;
}
return dCache;
}
在GetCurrentCache方法中,首先查看Session对象以判断是否存在标记为“dCache”的对象。如果存在,将从Session中拉出该对象并返回;如果不存在,则创建一个新的缓存对象并存储在Session中。这里的DataCache对象不会直接实例化,而是使用工厂对象DataCacheFactory来创建DataCache对象。在需要访问缓存时,事件将在页面上调用该方法。
bt_Add_Click事件通过调用Add方法向缓存添加一个新键/值对,如下面的代码所示:
protected void bt_Add_Click(object sender, EventArgs e)
{
var dCache=GetCurrentCache();
var key=keyTextbox.Text;
var val=valueTxtBox.Text;
if(key==""||val=="")return;
try
{
dCache.Add(key, val);
statusLabel.Text=
string.Format("成功地将\"{0}-{1}\"Add到缓存.",key, val);
}
catch(Exception ex)
{
statusLabel.Text=
string.Format("Error adding key{0}to cache:{1}",
key, ex.Message);
}
}
bt_Put_Click事件通过调用Put方法向缓存添加或更新一个新键/值对。与Add方法不同,缓存的Put方法检查该键是否已经存在于缓存中。如果该键不存在,则添加一个;如果该键存在,则更新该键的值。而缓存的Add方法则假设缓存中当前不存在该键。如下面的代码所示:
protected void bt_Put_Click(object sender, EventArgs e)
{
var dCache=GetCurrentCache();
var key=keyTextbox.Text;
var val=valueTxtBox.Text;
if(key==""||val=="")return;
try
{
dCache.Put(key, val);
statusLabel.Text=
string.Format("成功地将\"{0}-{1}\"put到缓存",key, val);
}
catch(Exception ex)
{
statusLabel.Text=
string.Format("Error putting key{0}to cache:{1}",
key, ex.Message);
}
}
bt_Get_Click事件通过调用Get方法来检索相应的值。如果该键存在于缓存中,则Get方法检索相应的值;如果缓存中不存在该键,则Get方法返回null。如下面的代码所示:
protected void bt_Get_Click(object sender, EventArgs e)
{
var dCache=GetCurrentCache();
var key=keyTextbox.Text;
if(key=="")return;
try
{
var val=dCache.Get(key).ToString();
valueTxtBox.Text=val;
statusLabel.Text=
string.Format("从缓存中Get\"{0}\"的值为:\"{1}\"",
key, val);
}
catch(Exception ex)
{
statusLabel.Text=
string.Format("Error getting key{0}from cache:{1}",
key, ex.Message);
}
}
bt_Remove_Click事件通过调用Remove方法来删除缓存中的键值。如下面的代码所示:
protected void bt_Remove_Click(object sender, EventArgs e)
{
var dCache=GetCurrentCache();
var key=keyTextbox.Text;
if(key=="")return;
try
{
dCache.Remove(key);
keyTextbox.Text=null;
valueTxtBox.Text=null;
statusLabel.Text=
string.Format("从缓存中将\"{0}\"Remove",key);
}
catch(Exception ex)
{
statusLabel.Text=
string.Format(ex.Message);
}
}
处理好这些事件之后,还需要在配置文件里进行相关配置。如下面的代码所示:
<configuration>
<configSections>
<section name="dataCacheClient"
type="Microsoft.Data.Caching.DataCacheClientSection,
CacheBaseLibrary"allowLocation="true"
allowDefinition="Everywhere"/>
</configSections>
<!——配置客户端缓存信息——>
<dataCacheClient deployment="simple">
<!——是否启用本地缓存以及缓存宿主——>
<localCache isEnabled="true"sync="TTLBased"ttlValue="300"/>
<hosts>
<host name="localhost"cachePort="22233"
cacheHostName="DistributedCacheService"/>
</hosts>
</dataCacheClient>
<system.web>
<compilation debug="true"targetFramework="4.0"/>
</system.web>
</configuration>
示例运行结果如图19-19所示。
图 19-19 示例运行结果
19.6.3 存储与检索复杂的数据
在Velocity中,可以缓存任何类型的数据。除了缓存一些简单的数据之外,还可以用来缓存诸如CLR对象、XML或者二进制数据等复杂类型数据。
下面的示例演示了如何缓存复杂类型数据((Eployee),与上面的示例一样,还是在页面代码中定义了四个操作按钮,分别演示缓存的四种操作,即Add、Put、Get与Remove操作。如下面的代码所示:
<form id="form1"runat="server">
<div>
<table>
<tr>
<td>
员工编号
</td>
<td>
<asp:TextBox ID="id"runat="server"/>
</td>
</tr>
<tr>
<td>
员工姓名
</td>
<td>
<asp:TextBox ID="name"runat="server"/>
</td>
</tr>
<tr>
<td>
家庭住址
</td>
<td>
<asp:TextBox ID="address"runat="server"/>
</td>
</tr>
<tr>
<td>
联系电话
</td>
<td>
<asp:TextBox ID="phone"runat="server"/>
</td>
</tr>
</table>
<table>
<tr>
<td>
<asp:Button runat="server"ID="bt_Add"
Text="Add"OnClick="bt_Add_Click"/>
<asp:Button runat="server"ID="bt_Put"
Text="Put"OnClick="bt_Put_Click"/>
<asp:Button runat="server"ID="bt_Get"
Text="Get"OnClick="bt_Get_Click"/>
<asp:Button runat="server"ID="bt_Remove"
Text="Remove"OnClick="bt_Remove_Click"/>
</td>
</tr>
</table>
<asp:Label runat="server"ID="StatusLabel"/>
</div>
</form>
在后台代码中,首先需要定义一个Employee类,该类必须要能够序列化。其次,定义四个事件处理程序,它们的实现方法与上面的示例相似,这里就不再继续逐一阐述。详细代码如代码清单19-6所示。
代码清单19-6 EmployeeData.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.Data.Caching;
namespace_19_2
{
[Serializable]
public class Employee
{
public String ID{get;set;}
public String Name{get;set;}
public String Address{get;set;}
public String Phone{get;set;}
}
public partial class EmployeeData:System.Web.UI.Page
{
private DataCache GetCurrentCache()
{
DataCache dCache;
if(Session["dCache"]!=null)
{
dCache=((DtaCache)Session["dCache"];
}
else
{
var factory=new DataCacheFactory();
dCache=factory.GetCache("default");
Session["dCache"]=dCache;
}
return dCache;
}
protected void Page_Load(object sender, EventArgs e)
{
}
protected void bt_Add_Click(object sender, EventArgs e)
{
var dCache=GetCurrentCache();
var_id=id.Text;
var_name=name.Text;
var_address=address.Text;
var_phone=phone.Text;
var emp=new Employee
{
ID=_id,
Name=_name,
Address=_address,
Phone=_phone,
};
try
{
dCache.Add(_id, emp);
StatusLabel.Text=
string.Format("成功地将\"{0}\"Add到缓存",_id);
}
catch(Exception ex)
{
StatusLabel.Text=
string.Format("Error adding employee{0}to cache:
{1}",_id, ex.Message);
}
}
protected void bt_Put_Click(object sender, EventArgs e)
{
var dCache=GetCurrentCache();
var_id=id.Text;
var_name=name.Text;
var_address=address.Text;
var_phone=phone.Text;
var emp=new Employee
{
ID=_id,
Name=_name,
Address=_address,
Phone=_phone,
};
try
{
dCache.Put(_id, emp);
StatusLabel.Text=
string.Format("成功地将\"{0}\"put到缓存",_id);
}
catch(Exception ex)
{
StatusLabel.Text=
string.Format("Error putting employee{0}
to cache:{1}",_id, ex.Message);
}
}
protected void bt_Get_Click(object sender, EventArgs e)
{
var dCache=GetCurrentCache();
try
{
var emp=((Eployee)dCache.Get(id.Text);
name.Text=emp.Name;
address.Text=emp.Address;
phone.Text=emp.Phone;
StatusLabel.Text=
string.Format("从缓存中Get\"{0}\"的值",emp.ID);
}
catch(Exception ex)
{
StatusLabel.Text=
string.Format("Error getting employee{0}and{1}",
id.Text, ex.Message);
}
}
protected void bt_Remove_Click(object sender, EventArgs e)
{
var dCache=GetCurrentCache();
var_id=id.Text;
try
{
dCache.Remove(_id);
id.Text=null;
name.Text=null;
address.Text=null;
phone.Text=null;
StatusLabel.Text=
string.Format("从缓存中将\"{0}\"Remove",_id);
}
catch(Exception ex)
{
StatusLabel.Text=string.Format(ex.Message);
}
}
}
}
示例运行结果如图19-20所示。
图 19-20 示例运行结果
19.6.4 使用分区与标签
在实际部署中,经常会出现多个应用程序共享同一个缓存集群的情况,这不可避免地会出现缓存键冲突,如上面的示例中使用ID作为缓存键。要解决这样的问题,可以使用Velocity中的分区功能,它会在逻辑上把各个命名缓存再进行分区,这样可以完全保持数据隔离。
在Velocity中,对分区的操作提供了如下三个方法,可以用于创建分区、删除分区以及清空分区中所有的对象。如下面的代码所示:
public void CreateRegion(string region, bool evictionOn);
public void RemoveRegion(string region);
public void ClearRegion(string region);
如下面的示例代码创建了一个名为“Employees”的分区,然后再调用Add方法指定数据将会缓存到哪个分区中:
var dCache=GetCurrentCache();
string regionName="Employees";
dCache.CreateRegion(regionName, false);
var emp=new Employee
{
ID="s001",
Name="马伟",
Address="陕西西安",
Phone="13571111111"
};
dCache.Add("s001",emp, regionName);
现在,就可以使用Get-CacheRegion命令来查看一下当前缓存集群中所有的分区信息。如图19-21所示,你能够看见刚刚创建的Employees分区:
当然,在检索缓存数据时,同样可以使用分区名称来进行检索。如下面的代码所示:
dCache.Get("s001","Employees");
除此之外,在Velocity缓存中,还允许对加入到缓存中的缓存项设置DataCacheTag,可以设置一个或者多个DataCacheTag。如果使用了DataCacheTag,就可以从多个方面对缓存项进行描述,这样在检索数据时,就可以根据DataCacheTag来一次检索多个缓存项。
图 19-21 查看分区
下面的示例代码演示了如何为缓存项设置DataCacheTag:
DataCache dCache=GetCurrentCache();
string regionName="Employees";
var emp1=new Employee
{
ID="s001",
Name="马伟",
Address="陕西西安",
Phone="13571111111"
};
List<DataCacheTag>tag=new List<DataCacheTag>();
tag.Add(new DataCacheTag("陕西西安"));
tag.Add(new DataCacheTag("马伟"));
dCache.Add(emp1.ID, emp1,tag, regionName);
通过在Add方法中设置好DataCacheTag之后,就可以根据需要使用下面三种方法对设置了DataCacheTag的缓存项进行检索:
public IEnumerable<KeyValuePair<string, object>>
GetObjectsByAllTags(List<DataCacheTag>tags, string region);
public IEnumerable<KeyValuePair<string, object>>
GetObjectsByAnyTag(List<DataCacheTag>tags, string region);
public IEnumerable<KeyValuePair<string, object>>
GetObjectsByTag(DataCacheTag tag, string region);
检索示例如下面的代码所示:
IEnumerable<KeyValuePair<string, object>>=
dCache.GetObjectsByTag(
new DataCacheTag("陕西西安"),regionName);
//或者
IEnumerable<KeyValuePair<string, object>>result1=
dCache.GetObjectsByAllTags(tag, regionName);
19.6.5 锁定模型
在Velocity中,它提供了一套悲观锁定模型,即在某个缓存项数据处理过程中,如果数据将处于锁定状态,则来自于其他客户端应用程序将无法对该缓存项进行处理。提供悲观锁定的方法主要有如下三个:
1)GetAndLock:获取缓存项并对数据加锁。该方法可以获取缓存项时并对数据进行加锁,此时如果其他客户端试图获取该数据并加锁(即调用GetAndLock方法)将会失败,而不会阻塞;但客户端如果只想获取数据(即调用Get方法),则会返回相应的数据。
2)PutAndUnlock:更新加锁的数据并释放锁。
3)Unlock:释放锁定。
使用GetAndLock方法可以指定锁过期时间,并且会有输出参数DataCacheLockHandle。该参数将会在PutAndUnlock方法或Unlock中来释放锁。如下代码所示:
DataCache dCache=GetCurrentCache();
DataCacheLockHandle handle;
Employee emp=((Eployee)dCache.GetAndLock("s001",
new TimeSpan(0,30,0),out handle);
var emp1=new Employee
{
ID="s001",
Name="马伟2",
Address="陕西西安",
Phone="13572222222"
};
dCache.PutAndUnlock(emp1.ID, emp1,handle,"Employees");