网站架构之缓存应用(2)实现篇
上一篇我主要总结了网站缓存中的一些基本概念,以及我对于网站架构缓存应用的架构实现思路,这篇主要分享下如何利用微软企业库来实现一二级缓存的缓存服务。
为了能够有效的管理缓存,需要对使用缓存方法上做一些规范,即要想使用缓存组件提供的服务,需要在指定的配置文件中按照一定的规则来配置缓存条目,不允许在配置之处使用缓存。下面先展示下一条Cache条目的配置:
<SubRegion name="default">
<Cache CacheMode="LocalCacheOnlyMode" Key="BlogListConfigKey" BufferType="AbsoluteTime" BufferTimeSeconds="300" CacheType="AbsoluteTime" CacheTimeMinutes="30" CachePriority="Normal"/>
</SubRegion>
</Region>
上面的代码中,其实由三部分构成:
1:主分区:Regin,如果一个网站分很多子系统,可以为每个子系统定义一个这样的主分区,例如食品频道Food,手机频道Mobile等;
2:子分区:SubRegion,主分区下面的子分区,即对子系统更加小的划分,可以根据子系统的功能来划分,例如产品列表页List,详细页Detail等;
3:缓存条目:Cache,指具体的一则缓存条目规则,这里的缓存条目规则并不是指某一条缓存设置,而是指一条缓存规则,与具体的缓存条目是一对多的关系。
<1>:CacheMode,设置缓存模式,是只有二级缓存还是即有一级缓存也有二级缓存,例如用户页面之间的信息沟通就只需要二级缓存,即缓存在web server上。而产品列表页的数据属于全局数据,就需要即采用二级缓存也需要一级缓存。
<2>:BufferType,指二级缓存的过期方式,分为绝对过期,滑动过期,文件依赖。
<3>:BufferTimeSeconds,二级缓存Timespan中的秒。
<4>:CacheType,一级缓存的过期方式,类型同BufferType.
<5>:CacheTimeMinutes,一级缓存Timespan中的分钟。
<6>:CachePriority,缓存的优先级。
二级缓存实现:
第一:IWebCacheProvider,缓存提供者接口,它公布了所有缓存组件需要的方法,接口之所以加上了ServeiceContract标签,是由于下面的一级缓存WCF服务也继承此接口的原因。小提示:WCF服务契约对于方法重载的实现和普通方式有小小区别,请注意OperationContract标签的定义。
public interface IWebCacheProvider
{
[OperationContract(Name = "Add")]
void Insert(string key, object value, string region, string subRegion);
[OperationContract(Name = "AddByAbsoluteTime")]
void Insert(string key, object value, string region, string subRegion, MyCacheItemPriority scavengingPriority, AbsoluteTimeCacheDependency absoluteTimeCacheDependency);
[OperationContract(Name = "AddBySlidingTime")]
void Insert(string key, object value, string region, string subRegion, MyCacheItemPriority scavengingPriority, SlidingTimeCacheDependency slidingTimeCacheDependency);
[OperationContract(Name = "AddByFile")]
void Insert(string key, object value, string region, string subRegion, MyCacheItemPriority scavengingPriority, FileCacheDependency fileCacheDependency);
[OperationContract]
void Delete(string key, string region, string subRegion);
[OperationContract]
object Get(string key, string region, string subRegion);
[OperationContract]
void Clear(string region);
[OperationContract]
int Count(string region);
}
第二:EntLibWebCacheProvider,微软企业库实现缓存实现类。代码并不贴了,基本就是利用企业库的缓存组件实现上面的接口。
第三:MyWebCacheServiceClient,提供缓存客户端的实例。包含了两个重要的属性:一级缓存实例,二级缓存实例。余下的就是调用EntLibWebCacheProvider来完成缓存的调用,以及根据缓存规则,选择操作一级缓存以及二级缓存。
说明:下面代码中的GetMemcachedWebCacheProvider是下篇文章会提到的利用memcached实现一级缓存,由于需要支持一级缓存在企业库以及memcached之间的切换才出现的逻辑。
IWebCacheProvider PrimaryCacheProvider;
//二级缓存
IWebCacheProvider SecondaryCacheProvider;
/// <summary>
/// 实例化二级缓存
/// </summary>
/// <param name="configFilePath"></param>
/// <returns></returns>
private IWebCacheProvider GetSecondaryCacheProvider()
{
IWebCacheProvider provider = null;
provider = WebCacheProviderFactory.GetEntLibWebCacheProvider(configFilePath);
return provider;
}
/// <summary>
/// 获取一级缓存
/// </summary>
/// <param name="hashKey"></param>
/// <param name="configFilePath"></param>
/// <returns></returns>
private IWebCacheProvider GetPrimaryCacheProvider(uint hashKey)
{
IWebCacheProvider provider = null;
string cacheType = WebConfig.ChannelConfig["CacheType"].ToString().ToLower();
switch (cacheType)
{
case "memcached":
provider = WebCacheProviderFactory.GetMemcachedWebCacheProvider(configFilePath);
break;
case "entlib":
provider = servicePool.GetServiceClient(hashKey) as IWebCacheProvider;
break;
}
return provider;
}
一级缓存的实现:由于一级缓存也采用微软企业库实现,而企业库本身是不具备分布式功能的,就算是memcached,本身也不具备分布式,而在于客户端的实现,所以企业库我们也可以实现分布式。
首先:我们把实现了缓存的组件以WCF服务形式分布出来,在多个服务器上部署,形成一个服务器群;
其实:实现分布式的客户端,让服务的请求均匀的分布到之前部署的WCF缓存服务器上。这里采用一致性hash算法实现,主要是根据key的hash值以及服务器数量来选择存储的服务器,这里贴些主要的实现代码,供参考:
1:定义一个hash的服务器实例集合,为每个服务器分配250个hash值,这里的值可以根据实际情况调整。
2:定义一个hash的服务实例key集合,用来统计所有服务器实例中的hash key。
3:创建服务器列表的hash值。
/// 重新设置服务器列表
/// </summary>
/// <param name="hosts">服务器列表</param>
internal void Setup(List<WebCacheServerInfo> hosts)
{
hostDictionary = new Dictionary<uint, isRoc.Common.Cache.CacheProvider.IWebCacheProvider>();
List<isRoc.Common.Cache.CacheProvider.IWebCacheProvider> clientList = new List<isRoc.Common.Cache.CacheProvider.IWebCacheProvider>();
List<uint> hostKeysList = new List<uint>();
foreach (WebCacheServerInfo host in hosts)
{
//创建客户端
isRoc.Common.Cache.CacheProvider.IWebCacheProvider client = ServiceProxyFactory.Create<isRoc.Common.Cache.CacheProvider.IWebCacheProvider>(host .HostUri );
//Create 250 keys for this pool, store each key in the hostDictionary, as well as in the list of keys.
for (int i = 0; i < 250; i++)
{
uint key = 0;
switch (this.HashAlgorithm)
{
case EHashAlgorithm.KetamaHash:
key = (uint)Math.Abs(KetamaHash.Generate(host.HostUri + "-" + i));
break;
case EHashAlgorithm.FnvHash32:
key = BitConverter.ToUInt32(new ModifiedFNV1_32().ComputeHash(Encoding.UTF8.GetBytes(host.HostUri + "-" + i)), 0);
break;
}
if (!hostDictionary.ContainsKey(key))
{
hostDictionary.Add(key, client);
hostKeysList.Add(key);
}
}
clientList.Add(client);
}
//Hostlist should contain the list of all pools that has been created.
clientArray = clientList.ToArray();
//Hostkeys should contain the list of all key for all pools that have been created.
//This array forms the server key continuum that we use to lookup which server a
//given item key hash should be assigned to.
hostKeysList.Sort();
hostKeysArray = hostKeysList.ToArray();
}
4:如何根据key值来查找具体的缓存服务实例呢,即具体的key存储在哪一台服务器上呢?根据传入的key值,计算出对应的hash值,然后经过特定的算法计算出服务器实例地址。
{
//Quick return if we only have one host.
if (clientArray.Length == 1)
{
return clientArray[0];
}
//New "ketama" host selection.
int i = Array.BinarySearch(hostKeysArray, hash);
//If not exact match...
if (i < 0)
{
//Get the index of the first item bigger than the one searched for.
i = ~i;
//If i is bigger than the last index, it was bigger than the last item = use the first item.
if (i >= hostKeysArray.Length)
{
i = 0;
}
}
return hostDictionary[hostKeysArray[i]];
}
总结:本文简单的介绍了如何利用微软企业库来实现具有两级缓存的缓存组件,上篇我提到过,实现一级缓存也可以采用memcached,采用memcached可以不用自己开发分布式客户端,目前有两个成熟的解决方案:1:Memcached.ClientLibrary2:EnyimMemcached。下篇我来介绍一级缓存如何通过memcached实现,以及如何让组件在一级缓存上即支持企业库也支持memcached。