C# 缓存的实现方式
缓存介绍
缓存,在一定程度上,是可以提高程序性能的一个解决方案,比如,我们从数据库读数据,如果每次都从数据库读取的话,每次都需要进行 网络IO操作,需要等待网络数据返回,如果在60s内,有成千上百个访问进行同样的数据进行查询,将会更加耗时耗力……如果,我们将第一个访问者查询的数据,先保存起来,然后60s内,其他访问者均读取保存起来的数据,这样不需要再去重新查询数据库,减少一定的网络操作,所以说,缓存,一定程度上可以提高程序性能!
这里不是做第三方比如Redis等的缓存实现,而是根据实际情况,基于C#上做一些环境变量的保存,方便项目使用。
1、系统全局变量
很多时候,在系统运行开始,需要对系统的运行参数进行保存,以便供全局使用。
代码如下:
public class PFTCacheObject
{
/// <summary>
/// 字典
/// </summary>
private static Dictionary<string, object> _dataDic = new Dictionary<string, object>();
/// <summary>
/// 定义一个静态变量来保存类的实例
/// </summary>
private static PFTCacheObject _session;
/// <summary>
/// 定义一个标识确保线程同步
/// </summary>
private static readonly object _locker = new object();
/// <summary>
/// 单例
/// </summary>
/// <returns>返回类型为Session</returns>
public static PFTCacheObject Instance
{
get
{
if (_session == null)
{
lock (_locker)
{
if (_session == null)// 如果类的实例不存在则创建,否则直接返回
{
_session = new PFTCacheObject();
}
}
}
return _session;
}
}
#region Remove 移除
/// <summary>
/// 删除成员
/// </summary>
/// <param name="name"></param>
public void Remove(string name)
{
_dataDic.Remove(name);
}
/// <summary>
/// 删除全部成员
/// </summary>
public void RemoveAll()
{
_dataDic.Clear();
}
#endregion
#region 本类的索引器
/// <summary>
/// 本类的索引器
/// </summary>
/// <returns>返回Object成员</returns>
public Object this[string index]
{
get
{
if (_dataDic.ContainsKey(index))
{
Object obj = (Object)_dataDic[index];
return obj;
}
return null;
}
set
{
if (_dataDic.ContainsKey(index))
{
_dataDic.Remove(index);
}
_dataDic.Add(index, value);
}
}
#endregion
}
这里使用一个静态变量的Dictionary来进行保存,所有项目均可以直接获取。
2、异步的数据缓存
在web上面,很多时候都是使用的HttpContext.Current.Items进行数据缓存,在.Net Framework框架上使用CallContext.LogicalSetData。在.Net Standard 上面CallContext.LogicalSetData已经不能使用了,替换他的方法是AsyncLocal。因为我们现在都是使用的是.Net Standard,并且我们项目并不仅仅是web,所以这里我们就只用使用AsyncLocal实现。
代码如下
public class PFTCallContext
{
#region 共享数据库连接
private static AsyncLocal<object> _asyncLocalConnectionOject = new AsyncLocal<object>();
/// <summary>
/// 设置共享数据库连接
/// </summary>
/// <param name="obj"></param>
public static void SetConnectionOject(object obj)
{
_asyncLocalConnectionOject.Value = obj;
}
/// <summary>
/// 获取共享数据库连接
/// </summary>
/// <returns></returns>
public static object GetConnectionOject()
{
return _asyncLocalConnectionOject.Value;
}
/// <summary>
/// 清空共享数据库连接
/// </summary>
public static void ClearConnectionOject()
{
_asyncLocalConnectionOject.Value = null;
}
#endregion
}
这里我们就先定义一个当前数据库连接对象的缓存。如果还需要其他的定义,你可以直接实现。
3、.Net Core 的MemoryCache
本来这块想使用.Net Framework框架中的cache的,但是.Net Core才是未来的大势,而且.Net Framework的cache已经有很多的实现方法了,所以这里我就直接用.Net Core 的MemoryCache。.Net Core蜗牛也在学习中,相关的API也在不断的研究,如果有问题,请大家帮忙纠正。
public static class PFTCache
{
public readonly static IMemoryCache _memoryCache;
static PFTCache()
{
_memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
}
#region 常规缓存
/// <summary>
/// 获取缓存的值
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static Object GetCache(string key)
{
return _memoryCache.Get(key);
}
/// <summary>
/// 移除缓存
/// </summary>
/// <param name="key"></param>
public static void Remove(string key)
{
_memoryCache.Remove(key);
}
/// <summary>
/// 设置缓存
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public static void SetCache(string key, Object value)
{
_memoryCache.GetOrCreate(key, entry =>
{
return value;
});
}
/// <summary>
/// 设置缓存(固定时间过期)
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="absoluteExpiration"></param>
public static void SetCacheAbsolute(string key, Object value, int absoluteExpiration)
{
_memoryCache.GetOrCreate(key, entry =>
{
entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(absoluteExpiration));
return value;
});
}
/// <summary>
/// 设置缓存(滚动时间过期)
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="slidingExpiration"></param>
public static void SetCacheSliding(string key, Object value, int slidingExpiration)
{
_memoryCache.GetOrCreate(key, entry =>
{
entry.SetSlidingExpiration(TimeSpan.FromSeconds(slidingExpiration));
return value;
});
}
#endregion
#region 文件依赖缓存
/// <summary>
/// 文件依赖缓存
/// </summary>
/// <param name="key"></param>
/// <param name="fullName"></param>
/// <returns></returns>
public static string DependentCacheFile(string key, string fullName)
{
var basePath = PFTFile.GetDirectoryName(fullName);
var fileName = PFTFile.GetFileName(fullName);
var fileProvider = new PhysicalFileProvider(basePath);
return _memoryCache.GetOrCreate(key, entry =>
{
entry.AddExpirationToken(fileProvider.Watch(fileName));
return PFTFile.IsExistFile(fullName) ? PFTFile.ReadFile(fullName) : string.Empty;
});
}
#endregion
}
4、ICache 实现
定义一个ICache 接口
public interface ICache
{
/// <summary>
/// 获取缓存项,当没有缓存时,使用factory提供的值
/// </summary>
/// <param name="key"></param>
/// <param name="factory"></param>
/// <returns></returns>
object Get(string key, Func<string, object> factory);
/// <summary>
/// 获取缓存项,没有缓存时返回默认数据
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
object GetOrDefault(string key);
/// <summary>
/// 设置缓存项并设置过期时间
/// </summary>
/// <param name="key">key</param>
/// <param name="value">值</param>
/// <param name="slidingExpireTime">多久未访问则失效</param>
/// <param name="absoluteExpireTime">超时失效</param>
void Set(string key, object value, TimeSpan? slidingExpireTime = null,TimeSpan?absoluteExpireTime=null);
/// <summary>
/// 移除缓存项
/// </summary>
/// <param name="key"></param>
void Remove(string key);
/// <summary>
/// 清空缓存
/// </summary>
void Clear();
}
该接口提供了获取缓存、设置缓存、移除缓存和清空缓存操作,且 get 方法,如果缓存中没有项,则可以通过 factory返回数据并保存到缓存!
缓存基类:CacheBase
public abstract class CacheBase : ICache
{
protected readonly object SyncObj = new object();
protected CacheBase()
{
}
public virtual object Get(string key, Func<string, object> factory)
{
var cacheKey = key;
var item = this.GetOrDefault(key);
if (item == null)
{
lock (this.SyncObj)// TODO: 为何要锁定
{
item = this.GetOrDefault(key);
if (item != null)
{
return item;
}
item = factory(key);
if (item == null)
{
return null;
}
this.Set(cacheKey, item);
}
}
return item;
}
public abstract object GetOrDefault(string key);
public abstract void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null);
public abstract void Remove(string key);
public abstract void Clear();
public virtual void Dispose()
{
}
}
缓存基类为虚类,只实现了 Get(string key, Func<string, object> factory)
方法, 这里进行了锁定,主要是为了多线程操作,在设置缓存的时候,只会有一个访问者在设置缓存项,其他方法均为虚方法,等待具体实现类实现
缓存具体实现类:DemoCache
public class DemoCache : CacheBase
{
private MemoryCache _memoryCache;
public DemoCache()
: base()
{
this._memoryCache = new MemoryCache("DemoCache");
}
public override object GetOrDefault(string key)
{
return this._memoryCache.Get(key);
}
public override void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
if (value == null)
{
throw new Exception("Can not insert null values to the cache!");
}
var cachePolicy = new CacheItemPolicy();
if (absoluteExpireTime != null)
{
cachePolicy.AbsoluteExpiration = DateTimeOffset.Now.Add(absoluteExpireTime.Value);
}
else if (slidingExpireTime != null)
{
cachePolicy.SlidingExpiration = slidingExpireTime.Value;
}
else
{
cachePolicy.AbsoluteExpiration = DateTimeOffset.Now.Add(TimeSpan.FromSeconds(60));
}
this._memoryCache.Set(key, value, cachePolicy);
}
public override void Remove(string key)
{
this._memoryCache.Remove(key);
}
public override void Clear()
{
// 将原来的释放,并新建一个cache
this._memoryCache.Dispose();
this._memoryCache = new MemoryCache("DemoCache");
}
public override void Dispose()
{
this._memoryCache.Dispose();
base.Dispose();
}
}
在具体实现类内,维护一个 MemoryCache, 其他方法,均操作MemoryCache 进行缓存操作!
在具体的设置缓存项中,设置了,如果过期时间为空的话,则设置多久为访问超时,如果两则都为空的话,则设置 定时超时,这里默认为 60 秒。
清空缓存的时候,则释放 memorycache,并新建具体cache实例来实现
使用缓存
定义一个方法,用来取数据
private static int GetCacheRandom(ICache cache)
{
var cacheItem = cache.Get("random",
(key) =>
{
Random random = new Random();
return random.Next(1, 1000);
});
return int.Parse(cacheItem.ToString());
}
这个方法,提供了取1到1000的随机值,并保存到缓存中。
定义一个现实函数,用来现实 何时取到的数字
private static void ShowCacheItem(int value)
{
Console.WriteLine("{0} 取数:{1}",DateTime.Now.ToString("HH:mm:ss"), value);
}
具体使用
static void Main(string[] args)
{
ICache cache = new DemoCache();
var cacheItem = GetCacheRandom(cache);
Console.Write("第一次取值 ");
ShowCacheItem(cacheItem);
Stopwatch watch=new Stopwatch();
watch.Start();
while (true)
{
if (watch.ElapsedMilliseconds < 10000)
{
continue;
}
cacheItem = GetCacheRandom(cache);
Console.Write("10s后取值 ");
ShowCacheItem(cacheItem);
break;
}
while (true)
{
if (watch.ElapsedMilliseconds < 40000)
{
continue;
}
cacheItem = GetCacheRandom(cache);
Console.Write("40s后取值 ");
ShowCacheItem(cacheItem);
break;
}
while (true)
{
if (watch.ElapsedMilliseconds < 70000)
{
continue;
}
cacheItem = GetCacheRandom(cache);
Console.Write("70s后取值,此时应该是新值 ");
ShowCacheItem(cacheItem);
break;
}
watch.Stop();
Console.WriteLine("输入任意键退出!");
Console.ReadKey();
}
首先建立 DemoCache的实例,然后获取一次数值,然后10s取一次数据,40s取一次数据,70s取一次数据,如果缓存生效,则前三次,即第一次,10s取得一次,40s取得数字,应该是一样的,70s取得数据应该和前三次不一样。
这是使用C#自带缓存,还可以尝试试用一下 Redis