内存缓存MemoryCache
内存缓存MemoryCache实现了ICache接口,Redis同样实现了ICache接口,两者在缓存操作上达到了高度抽象统一。应用设计时一律使用ICache接口,开发环境装配为MemoryCache,生产环境根据分布式需要可以装配为Redis。如果应用系统没有分布式需求,继续使用MemoryCache更好。
超高性能
MemoryCache核心是并行字典ConcurrentDictionary,由于省去了序列化和网络通信,使得它具有千万级超高性能(普通台式机实测2.87亿tps)。
MemoryCache支持过期时间,默认容量10万个,未过期key超过该值后,每60秒根据LRU清理溢出部分。
常用于进程内千万级以下数据缓存场景。
本机测试数据如下(I9-10900K):
-
Test v1.0.0.1130 Build 2021-01-31 19:33:32 .NETCoreApp,Version=v5.0
-
Test
-
Memory性能测试[顺序],批大小[0],逻辑处理器 20 个
-
测试 10,000,000 项, 1 线程
-
赋值 耗时 934ms 速度 10,706,638 ops
-
读取 耗时 989ms 速度 10,111,223 ops
-
删除 耗时 310ms 速度 32,258,064 ops
-
累加 耗时 584ms 速度 17,123,287 ops
-
测试 20,000,000 项, 2 线程
-
赋值 耗时 927ms 速度 21,574,973 ops
-
读取 耗时 1,024ms 速度 19,531,250 ops
-
删除 耗时 319ms 速度 62,695,924 ops
-
累加 耗时 594ms 速度 33,670,033 ops
-
测试 40,000,000 项, 4 线程
-
赋值 耗时 1,011ms 速度 39,564,787 ops
-
读取 耗时 1,039ms 速度 38,498,556 ops
-
删除 耗时 1,636ms 速度 24,449,877 ops
-
累加 耗时 608ms 速度 65,789,473 ops
-
测试 80,000,000 项, 8 线程
-
赋值 耗时 989ms 速度 80,889,787 ops
-
读取 耗时 1,227ms 速度 65,199,674 ops
-
删除 耗时 1,858ms 速度 43,057,050 ops
-
累加 耗时 675ms 速度 118,518,518 ops
-
测试 200,000,000 项, 20 线程
-
赋值 耗时 1,644ms 速度 121,654,501 ops
-
读取 耗时 1,807ms 速度 110,680,686 ops
-
删除 耗时 2,936ms 速度 68,119,891 ops
-
累加 耗时 1,569ms 速度 127,469,725 ops
-
测试 200,000,000 项, 64 线程
-
赋值 耗时 1,686ms 速度 118,623,962 ops
-
读取 耗时 1,877ms 速度 106,553,010 ops
-
删除 耗时 695ms 速度 287,769,784 ops
-
累加 耗时 1,585ms 速度 126,182,965 ops
-
总测试数据:2,200,000,042
ICache接口
ICache是缓存抽象接口,主要实现是MemoryCache和Redis
-
/// <summary>缓存接口</summary>
-
public interface ICache
-
{
-
#region 属性
-
/// <summary>名称</summary>
-
String Name { get; }
-
/// <summary>默认缓存时间。默认0秒表示不过期</summary>
-
Int32 Expire { get; set; }
-
/// <summary>获取和设置缓存,永不过期</summary>
-
/// <param name="key"></param>
-
/// <returns></returns>
-
Object this[String key] { get; set; }
-
/// <summary>缓存个数</summary>
-
Int32 Count { get; }
-
/// <summary>所有键</summary>
-
ICollection<String> Keys { get; }
-
#endregion
-
#region 基础操作
-
/// <summary>是否包含缓存项</summary>
-
/// <param name="key"></param>
-
/// <returns></returns>
-
Boolean ContainsKey(String key);
-
/// <summary>设置缓存项</summary>
-
/// <param name="key">键</param>
-
/// <param name="value">值</param>
-
/// <param name="expire">过期时间,秒。小于0时采用默认缓存时间<seealso cref="Expire"/></param>
-
/// <returns></returns>
-
Boolean Set<T>(String key, T value, Int32 expire = -1);
-
/// <summary>设置缓存项</summary>
-
/// <param name="key">键</param>
-
/// <param name="value">值</param>
-
/// <param name="expire">过期时间</param>
-
/// <returns></returns>
-
Boolean Set<T>(String key, T value, TimeSpan expire);
-
/// <summary>获取缓存项</summary>
-
/// <param name="key">键</param>
-
/// <returns></returns>
-
T Get<T>(String key);
-
/// <summary>批量移除缓存项</summary>
-
/// <param name="keys">键集合</param>
-
/// <returns></returns>
-
Int32 Remove(params String[] keys);
-
/// <summary>清空所有缓存项</summary>
-
void Clear();
-
/// <summary>设置缓存项有效期</summary>
-
/// <param name="key">键</param>
-
/// <param name="expire">过期时间</param>
-
Boolean SetExpire(String key, TimeSpan expire);
-
/// <summary>获取缓存项有效期</summary>
-
/// <param name="key">键</param>
-
/// <returns></returns>
-
TimeSpan GetExpire(String key);
-
#endregion
-
#region 集合操作
-
/// <summary>批量获取缓存项</summary>
-
/// <typeparam name="T"></typeparam>
-
/// <param name="keys"></param>
-
/// <returns></returns>
-
IDictionary<String, T> GetAll<T>(IEnumerable<String> keys);
-
/// <summary>批量设置缓存项</summary>
-
/// <typeparam name="T"></typeparam>
-
/// <param name="values"></param>
-
/// <param name="expire">过期时间,秒。小于0时采用默认缓存时间<seealso cref="Expire"/></param>
-
void SetAll<T>(IDictionary<String, T> values, Int32 expire = -1);
-
/// <summary>获取列表</summary>
-
/// <typeparam name="T">元素类型</typeparam>
-
/// <param name="key">键</param>
-
/// <returns></returns>
-
IList<T> GetList<T>(String key);
-
/// <summary>获取哈希</summary>
-
/// <typeparam name="T">元素类型</typeparam>
-
/// <param name="key">键</param>
-
/// <returns></returns>
-
IDictionary<String, T> GetDictionary<T>(String key);
-
/// <summary>获取队列</summary>
-
/// <typeparam name="T">元素类型</typeparam>
-
/// <param name="key">键</param>
-
/// <returns></returns>
-
IProducerConsumer<T> GetQueue<T>(String key);
-
/// <summary>获取栈</summary>
-
/// <typeparam name="T">元素类型</typeparam>
-
/// <param name="key">键</param>
-
/// <returns></returns>
-
IProducerConsumer<T> GetStack<T>(String key);
-
/// <summary>获取Set</summary>
-
/// <typeparam name="T"></typeparam>
-
/// <param name="key"></param>
-
/// <returns></returns>
-
ICollection<T> GetSet<T>(String key);
-
#endregion
-
#region 高级操作
-
/// <summary>添加,已存在时不更新</summary>
-
/// <typeparam name="T">值类型</typeparam>
-
/// <param name="key">键</param>
-
/// <param name="value">值</param>
-
/// <param name="expire">过期时间,秒。小于0时采用默认缓存时间<seealso cref="Cache.Expire"/></param>
-
/// <returns></returns>
-
Boolean Add<T>(String key, T value, Int32 expire = -1);
-
/// <summary>设置新值并获取旧值,原子操作</summary>
-
/// <remarks>
-
/// 常常配合Increment使用,用于累加到一定数后重置归零,又避免多线程冲突。
-
/// </remarks>
-
/// <typeparam name="T">值类型</typeparam>
-
/// <param name="key">键</param>
-
/// <param name="value">值</param>
-
/// <returns></returns>
-
T Replace<T>(String key, T value);
-
/// <summary>尝试获取指定键,返回是否包含值。有可能缓存项刚好是默认值,或者只是反序列化失败,解决缓存穿透问题</summary>
-
/// <typeparam name="T">值类型</typeparam>
-
/// <param name="key">键</param>
-
/// <param name="value">值。即使有值也不一定能够返回,可能缓存项刚好是默认值,或者只是反序列化失败</param>
-
/// <returns>返回是否包含值,即使反序列化失败</returns>
-
Boolean TryGetValue<T>(String key, out T value);
-
/// <summary>累加,原子操作</summary>
-
/// <param name="key">键</param>
-
/// <param name="value">变化量</param>
-
/// <returns></returns>
-
Int64 Increment(String key, Int64 value);
-
/// <summary>累加,原子操作</summary>
-
/// <param name="key">键</param>
-
/// <param name="value">变化量</param>
-
/// <returns></returns>
-
Double Increment(String key, Double value);
-
/// <summary>递减,原子操作</summary>
-
/// <param name="key">键</param>
-
/// <param name="value">变化量</param>
-
/// <returns></returns>
-
Int64 Decrement(String key, Int64 value);
-
/// <summary>递减,原子操作</summary>
-
/// <param name="key">键</param>
-
/// <param name="value">变化量</param>
-
/// <returns></returns>
-
Double Decrement(String key, Double value);
-
#endregion
-
#region 事务
-
/// <summary>提交变更。部分提供者需要刷盘</summary>
-
/// <returns></returns>
-
Int32 Commit();
-
/// <summary>申请分布式锁</summary>
-
/// <param name="key">要锁定的key</param>
-
/// <param name="msTimeout"></param>
-
/// <returns></returns>
-
IDisposable AcquireLock(String key, Int32 msTimeout);
-
#endregion
-
#region 性能测试
-
/// <summary>多线程性能测试</summary>
-
/// <param name="rand">随机读写。顺序,每个线程多次操作一个key;随机,每个线程每次操作不同key</param>
-
/// <param name="batch">批量操作。默认0不分批,分批仅针对随机读写,对顺序读写的单key操作没有意义</param>
-
Int64 Bench(Boolean rand = false, Int32 batch = 0);
-
#endregion
-
}
基本用法
添删改查基本功能,Get/Set/Count/ContainsKey/Remove
-
var ic = new MemoryCache();
-
var key = "Name";
-
var key2 = "Company";
-
ic.Set(key, "大石头");
-
ic.Set(key2, "新生命");
-
Assert.Equal("大石头", ic.Get<String>(key));
-
Assert.Equal("新生命", ic.Get<String>(key2));
-
var count = ic.Count;
-
Assert.True(count >= 2);
-
// Keys
-
var keys = ic.Keys;
-
Assert.True(keys.Contains(key));
-
// 过期时间
-
ic.SetExpire(key, TimeSpan.FromSeconds(1));
-
var ts = ic.GetExpire(key);
-
Assert.True(ts.TotalSeconds > 0 && ts.TotalSeconds < 2, "过期时间");
-
var rs = ic.Remove(key2);
-
Assert.Equal(1, rs);
-
Assert.False(ic.ContainsKey(key2));
-
ic.Clear();
-
Assert.True(ic.Count == 0);
其中Set的第三个参数支持过期时间,单位秒。
集合操作
SetAll/GetAll 是高吞吐的关键,其中SetAll第二参数支持过期时间,单位秒
-
var ic = new MemoryCache();
-
var dic = new Dictionary<String, String>
-
{
-
["111"] = "123",
-
["222"] = "abc",
-
["大石头"] = "学无先后达者为师"
-
};
-
ic.SetAll(dic);
-
var dic2 = ic.GetAll<String>(dic.Keys);
-
Assert.Equal(dic.Count, dic2.Count);
-
foreach (var item in dic)
-
{
-
Assert.Equal(item.Value, dic2[item.Key]);
-
}
高级操作
MemoryCache有几个非常好用的高级操作,全部都是线程安全:
-
Add。添加,已存在时不更新,常用于锁争夺。例如,可用于判断指定订单是否处理过,加上过期时间,就是我们经常说的多少小时去重。
-
Replace。设置新值并获取旧值,原子操作
-
TryGetValue。尝试获取指定键,返回是否包含值。有可能缓存项刚好是默认值
-
Increment。累加
-
Decrement。累减
缓存过期策略
MemoryCache内置LRU淘汰算法,当缓存项超过最大值Capacity(默认10万)时,剔除最久未使用的缓存项,以避免内存占用过大。
缓存项未达到最大值Capacity时,MemoryCache定时检查并剔除过期项。


浙公网安备 33010602011771号