设为首页 收藏本站
查看: 932|回复: 0

[经验分享] Memcached进行缓存层设计

[复制链接]

尚未签到

发表于 2015-9-1 04:27:04 | 显示全部楼层 |阅读模式
  正在考虑web应用缓存层的设计,参考了不少资料,估计还是需要用到相对成熟应用广泛的分布式缓存Memcached。在.net平台上早就有相对成熟的Memcached客户端产品,如BeITMemcached和EnyimMemcached,业余时间看了一下源码,自己分析并调用一下并不困难。这里简单介绍一下利用Memcached的一个简单的缓存层设计,示例代码基于EnyimMemcached,下面以贴代码为主。

一、公共缓存接口
  分析asp.net web caching的缓存类,我们大致可以抽象出如下几个接口方法:


DSC0000.gif
namespace DotNet.Common.EnyimCache
{
/// <summary>
/// memcached公共缓存调用方法接口(读)
/// </summary>
public interface ICacheReaderService
{
/// <summary>
/// 返回指定key的对象
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
object Get(string key);
/// <summary>
/// 返回指定key的对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
T Get<T>(string key);
/// <summary>
/// 是否存在
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool isExists(string key);
}
/// <summary>
/// memcached公共缓存调用方法接口(写)
/// </summary>
public interface ICacheWriterService
{
/// <summary>
/// 缓存有效间隔时间 (以分钟为单位)
/// </summary>
int TimeOut { set; get; }
/// <summary>
/// 添加指定key的对象
/// </summary>
/// <param name="key"></param>
/// <param name="obj"></param>
void Add(string key, object obj);
/// <summary>
/// 添加指定key的对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="obj"></param>
void Add<T>(string key, T obj);
/// <summary>
/// 移除指定key的对象
/// </summary>
/// <param name="key"></param>
bool Remove(string key);
/// <summary>
/// 修改指定key的对象
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool Modify(string key, object destObj);
/// <summary>
/// 清空缓存
/// </summary>
/// <returns></returns>
bool Release();
}
}

  看命名就知道,增删改查是也。根据个人使用缓存的经验,修改操作通常是不需要的,如果确实需要修改缓存数据,直接删除然后添加就是改了。
  还有,你可能会问,这里为什么要定义两个接口?原因主要是考虑到读操作(查询)是经常使用的,而写操作(增删改)相对较少,所以也把它们设计成读写分离的方式。

二、缓存服务实现
  这里就需要调用Memcached客户端封装好的调用方法,实现增删改查等方法。



using System;
namespace DotNet.Common.EnyimCache
{
using Enyim.Caching.Memcached;
public class CacheReaderService : BaseService, ICacheReaderService
{
public int TimeOut
{
get;
set;
}
public CacheReaderService()
{
}
public object Get(string key)
{
object obj = null;
Client.TryGet(key, out obj);
return obj;
}
public T Get<T>(string key)
{
object obj = Get(key);
T result = default(T);
if (obj != null)
{
result = (T)obj;
}
return result;
}
public bool isExists(string key)
{
object obj = Get(key);
return (obj == null) ? false : true;
}
}
public class CacheWriterService : BaseService, ICacheWriterService
{
public int TimeOut
{
get;
set;
}
public CacheWriterService()
{
}
public CacheWriterService(int timeOut)
{
this.TimeOut = timeOut;
}
public void Add(string key, object obj)
{
if (TimeOut > 0)
{
Client.Store(StoreMode.Add, key, obj, DateTime.Now.AddMinutes(TimeOut));
}
else
{
Client.Store(StoreMode.Add, key, obj);
}
}
public void Add<T>(string key, T obj)
{
if (TimeOut > 0)
{
Client.Store(StoreMode.Add, key, obj, DateTime.Now.AddMinutes(TimeOut));
}
else
{
Client.Store(StoreMode.Add, key, obj);
}
}
public bool Remove(string key)
{
return Client.Remove(key);
}
public bool Modify(string key, object destObj)
{
return Client.Store(StoreMode.Set, key, destObj);
}
/// <summary>
/// 清空缓存 TO DO
/// </summary>
/// <returns></returns>
public bool Release()
{
throw new NotImplementedException();
}
}
}

  基类里初始化一个MemcachedClient示例Client,这个Client的方法里封装了较多的函数。查看源码可以知道,它们本质上都是
向Memcached服务端发送相关指令(run
command),然后解析返回的二进制数据,如果您熟悉memcached所使用的协议,理解起来应该会相当简单。本文示例只使用了客户端提供的几个方
法。
  同时要注意,在实现具体缓存服务的时候,CacheWriterService有两个构造函数,其中带参数的是为缓存显式指定过期时间。这个参数在实际应用中通常需要配置,显然是比较灵活一些的。
  备注:在接口中有一个函数Release,本来的目标是清空所有的缓存数据,但是客户端没有直接提供对应的函数,如果您有好的方法,请不吝赐教。

三、简单的读写测试
  贴一下字符串、时间、单个类和集合的增删改查示例代码:



            ICacheWriterService writer = CacheBuilder.GetWriterService();//writer 使用memcached默认过期时间
ICacheReaderService reader = CacheBuilder.GetReaderService();//reader
#region 字符串
string strKey = "hello";
bool isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
writer.Add(strKey, "hello world"); //添加
Console.WriteLine("Add key {0}, value:hello world", strKey);
bool isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
string result = reader.Get(strKey) as string;//查询
Console.WriteLine("Get key {0}:{1}", strKey, result);
bool isModify = writer.Modify(strKey, "Hello Memcached!");//修改
Console.WriteLine("Modify key {0}, value:Hello Memcached. The result is:{1}", strKey, isModify);
result = reader.Get<string>(strKey);
Console.WriteLine("Generic get key {0}:{1}", strKey, result);
isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
result = reader.Get(strKey) as string;
Console.WriteLine("Get key {0}:{1}", strKey, result);
result = reader.Get<string>(strKey);
Console.WriteLine("Generic get key {0}:{1}", strKey, result);
Console.WriteLine();
Console.WriteLine("===========================================");
Console.Read();
#endregion
#region 时间
DateTime dtNow = DateTime.Now;
strKey = "datetime";
isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
writer.Add(strKey, dtNow); //添加
Console.WriteLine("Add key {0}, value:{1}", strKey, dtNow);
isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
DateTime dt = (DateTime)reader.Get(strKey);//查询
Console.WriteLine("Get key {0}:{1}", strKey, dt);
dt = reader.Get<DateTime>(strKey);
Console.WriteLine("Generic get key {0}:{1}", strKey, dt);
isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey));
Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get<DateTime>(strKey));//default(datetime)
Console.WriteLine();
Console.WriteLine("===========================================");
Console.Read();
#endregion
#region 类
dtNow = DateTime.Now;
Province province = new Province(13579, "江苏", dtNow, dtNow);
strKey = string.Format("{0}_{1}", province.GetType().Name, province.Id);//省
isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
writer.Add(strKey, province); //添加
Console.WriteLine("Add key {0}, value:{1}", strKey, dtNow);
isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
Province queryProvince = (Province)reader.Get(strKey);//查询
Console.WriteLine("Get key {0}:{1}", strKey, queryProvince.ProvinceName);
queryProvince = reader.Get<Province>(strKey);
Console.WriteLine("Generic get key {0}:{1}", strKey, queryProvince.ProvinceName);
isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey));
Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get<Province>(strKey));
Console.WriteLine();
Console.WriteLine("===========================================");
Console.Read();
#endregion
#region 集合(列表)
dtNow = DateTime.Now;
IList<City> listCities = new List<City>();
City city = new City(135, province.Id, "南京", "210000", dtNow, dtNow);
listCities.Add(city);
city = new City(246, province.Id, "苏州", "215000", dtNow, dtNow);
listCities.Add(city);
strKey = string.Format("List_{0}_{1}_{2}", province.GetType().Name, province.Id, city.GetType().Name);//省份对应城市
isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
writer.Add(strKey, listCities); //添加
Console.WriteLine("Add key {0}, value:", strKey);
foreach (var item in listCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName);
}
isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
IList<City> queryCities = reader.Get(strKey) as IList<City>;//查询
Console.WriteLine("Get key {0}:", strKey);
foreach (var item in queryCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName);
}
queryCities = reader.Get<IList<City>>(strKey);
Console.WriteLine("Generic get key {0}:", strKey);
foreach (var item in queryCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Id, item.CityName);
}
isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey));
Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get<IList<City>>(strKey));
Console.WriteLine();
Console.WriteLine("===========================================");
Console.Read();
#endregion
#region 集合(字典)
dtNow = DateTime.Now;
IDictionary<int, City> dictCities = new Dictionary<int, City>();
city = new City(123, province.Id, "镇江", "212000", dtNow, dtNow);
dictCities.Add(city.Id, city);
city = new City(321, province.Id, "扬州", "225000", dtNow, dtNow);
dictCities.Add(city.Id, city);
strKey = string.Format("Dictionary_{0}_{1}_{2}", province.GetType().Name, province.Id, city.GetType().Name);//省份对应城市
isOK = writer.Remove(strKey); //移除
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
writer.Add(strKey, dictCities); //添加
Console.WriteLine("Add key {0}, value:", strKey);
foreach (var item in dictCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName);
}
isExists = reader.isExists(strKey);//是否存在
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
IDictionary<int, City> queryDictCities = reader.Get(strKey) as IDictionary<int, City>;//查询
Console.WriteLine("Get key {0}:", strKey);
foreach (var item in queryDictCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName);
}
queryDictCities = reader.Get<IDictionary<int, City>>(strKey);
Console.WriteLine("Generic get key {0}:", strKey);
foreach (var item in queryDictCities)
{
Console.WriteLine("CityId:{0} CityName:{1}", item.Key, item.Value.CityName);
}
isOK = writer.Remove(strKey);
Console.WriteLine("Removed key {0}:{1}", strKey, isOK);
isExists = reader.isExists(strKey);
Console.WriteLine("Key {0} exists:{1}", strKey, isExists);
Console.WriteLine("Get key {0}:{1}", strKey, reader.Get(strKey));
Console.WriteLine("Generic get key {0}:{1}", strKey, reader.Get<IDictionary<int, City>>(strKey));
Console.WriteLine();
Console.WriteLine("===========================================");
Console.Read();
#endregion

  这里就不贴全部代码了,文章最后有示例可以下载。
  在我的简单测试中,对常见基础数据类型如(字符串、数组、数字和时间)、集合(列表和字典)都有良好的表现,对datatable和dataset同样表现不俗,但是不太建议直接缓存这两种重粒度的类型。
  在显式指定过期时间的示例中,指定过期时间是一分钟,但是memcached实际过期时间有时候好像会多于一分钟,估计是系统内部的延迟。
  在本地计算机上进行10万次循环添加缓存的过程中,发现系统内存果然增加的非常厉害。然后查询性能并没有显著下降,也许和我的单机测试环境有关,所以我认为测试结果并没有说服力,要知道,memcached的优势是它的分布式缓存实现。
  有人发现如何保证缓存系统的键唯一也非常令人头疼。同样的缓存框架,不同项目不同开发者如何保证自己程序添加的缓存键唯一呢?有一种简单方法就是通
过拼接字符串成为有意义的主键,比如按照项目名、命名空间、类名、数据库中的主键组合构成主键等等。当然了,在查询的时候也要自己封装特定格式的字符串主
键。个人感觉确实是一个行之有效的方法。
  demo下载:SimpleCacheApp
  
作者:Jeff Wong
出处:http://jeffwongishandsome.cnblogs.com/
本文版权归作者和博客园共有,欢迎围观转载。转载时请您务必在文章明显位置给出原文链接,谢谢您的合作。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-107322-1-1.html 上篇帖子: 更快bobhash, 比time33快 (memcached也使用) 下篇帖子: Memcached在.NET中的应用
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表