想法背景
在做小型单体,用户量,并发量都不大的项目时,有需要用到少量缓存的需求,同时希望安装部署简单,那么直接上Redis就不合适了,毕竟Redis需要独立安装,那么实现一个可集成到项目中,简单轻量的缓存,是一个不错的选择
特点说明
无依赖 .net Core 6/7/8/9/10 都能用
支持并发
实现了简单的过期数据删除
适合小型单体项目,少量缓存,如果需要复杂的缓存功能,还是用Redis更好
在小型单体项目,用户量不大的情况下,基本够用
上代码
/// <summary>
/// 一个简单的缓存
/// 主要用于存放 验证码/登录Token 等短期内容,
/// 复杂的缓存操作,请使用 Redis
/// </summary>
public class CacheBase
{
/// <summary>
/// 缓存持久化保存路径
/// </summary>
public string? SavePath = null;
/// <summary>
/// 锁
/// </summary>
private static readonly object _persistLock = new object();
/// <summary>
/// 读取指定键的值
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string? this[string key]
{
get
{
if (!List.ContainsKey(key)) { return null; }
var data = Get(key);
return data;
}
}
/// <summary>
/// 缓存集合
/// </summary>
private ConcurrentDictionary<string, CacheItem> List = new ConcurrentDictionary<string, CacheItem>();
/// <summary>
/// 构造函数
/// </summary>
/// <param name="savePath"></param>
public CacheBase(string savePath)
{
SavePath = savePath;
// 创建目录
var dir = Path.GetDirectoryName(savePath);
Directory.CreateDirectory(dir);
// 从文件读取
if (!string.IsNullOrEmpty(SavePath))
{
var text= Text.Read(SavePath);
if (!string.IsNullOrEmpty(text))
{
List = text.ToEntity<ConcurrentDictionary<string, CacheItem>>();
}
}
}
/// <summary>
/// 持久化缓存
/// </summary>
/// <returns></returns>
public async Task PersistCache()
{
lock (_persistLock)
{
// 保存到文件
if (!string.IsNullOrEmpty(SavePath))
{
Text.Write(List.ToJson(), SavePath);
}
}
}
/// <summary>
/// 删除缓存
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task Delete(string key)
{
if (List.ContainsKey(key))
{
this.List.TryRemove(key, out CacheItem i);
}
PersistCache();
}
/// <summary>
/// 获取所有缓存
/// </summary>
/// <returns></returns>
public allCache GetAll()
{
DelItem();
return new allCache()
{
Items = List,
Count = List.Count,
};
}
/// <summary>
/// 获取缓存
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public T? Get<T>(string key)
{
var item = Get(key);
if (item != null)
{
return item.ToEntity<T>();
}
return default;
}
/// <summary>
/// 获取缓存
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string? Get(string key)
{
DelItem();
var dt = DateTime.Now;
if (List.ContainsKey(key))
{
var item = List[key];
if (item.TTL == null)
{
return item.Value;
}
if (item.TTL > dt)
{
return item.Value;
}
return null;
}
return null;
}
/// <summary>
/// 判断缓存是否存在
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool ContainsKey(string key)
{
if (List.ContainsKey(key))
{
return true;
}
return false;
}
/// <summary>
/// 上次清理时间
/// </summary>
public DateTime _lastCleanupTime = DateTime.MinValue;
/// <summary>
/// 删除过期的缓存
/// </summary>
/// <returns></returns>
private async Task DelItem()
{
var dt = DateTime.Now;
// 满足条件时清理一次,降低系统的负载
if (List.Count % 100 == 0 || (DateTime.Now - _lastCleanupTime).TotalMinutes >= 5)
{
// 收集所有需要移除的键
var keysToRemove = List.Where(x => x.Value.TTL < DateTime.Now).Select(x => x.Key).ToList();
// 移除过期的键
foreach (var item in keysToRemove)
{
List.TryRemove(item, out CacheItem i);
}
_lastCleanupTime = dt;
PersistCache();
}
}
/// <summary>
/// 添加值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="ttl"></param>
/// <returns></returns>
public async Task Set<T>(string key, T value, int ttl = 0)
{
var val = value.ToJson();
Set(key, val, ttl);
}
/// <summary>
/// 添加值
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="ttl"></param>
public async Task Set(string key, string value, int ttl = 0)
{
DelItem();
DateTime? _ttl;
if (ttl < 0) ttl = 0;
if (ttl == 0)
{
_ttl = null;
}
else
{
_ttl = DateTime.Now.AddSeconds(ttl);
}
var item = new CacheItem()
{
TTL = _ttl,
Value = value,
};
if (List.ContainsKey(key))
{
List[key] = item;
}
else
{
List.AddOrUpdate(key, item, (k, v) => v);
}
PersistCache();
}
}
/// <summary>
/// 缓存项
/// </summary>
public class CacheItem
{
/// <summary>
/// 有效期
/// </summary>
public DateTime? TTL { get; set; }
/// <summary>
/// 缓存值
/// </summary>
public string? Value { get; set; }
}
/// <summary>
/// 所有缓存
/// </summary>
public class allCache
{
/// <summary>
/// 缓存数量
/// </summary>
public int Count { get; set; }
/// <summary>
/// 所有缓存
/// </summary>
public ConcurrentDictionary<string, CacheItem> Items { get; set; }
}
实现多个实例
public class Cache1: CacheBase
{
public Cache1():base("wwwroot/Cache/13.json")
{
}
}
public class Cache2: CacheBase
{
public Cache2():base("wwwroot/Cache/13.json")
{
}
}
注
上面代码中的部分内容说明
ToJson
方法将实体转为json字符串
ToEntity
方法将json字符串转为指定的类型
Text.Read
方法从指定路径的文件读取文本
Text.Write
方法将字符串写入到指定路径的文件
上面代码中没有这些内容的实现,请自行实现替代
持久化以json明文保存,请注意数据安全,或者自行实现加密保存
不适合大型/分布式/需要复杂缓存 的项目