c# - 寻找一种在缓存时减少锁定的方法
问题描述
我正在使用下面的代码来缓存项目。这是非常基本的。
我遇到的问题是,每次它缓存一个项目时,部分代码都会锁定。因此,每小时大约有一百万件物品到达,这是一个问题。
我已经尝试为每个cacheKey创建一个静态锁对象字典,以便锁定是细粒度的,但这本身就成为管理它们到期等的问题......
有没有更好的方法来实现最小锁定?
private static readonly object cacheLock = new object();
public static T GetFromCache<T>(string cacheKey, Func<T> GetData) where T : class {
// Returns null if the string does not exist, prevents a race condition
// where the cache invalidates between the contains check and the retrieval.
T cachedData = MemoryCache.Default.Get(cacheKey) as T;
if (cachedData != null) {
return cachedData;
}
lock (cacheLock) {
// Check to see if anyone wrote to the cache while we where
// waiting our turn to write the new value.
cachedData = MemoryCache.Default.Get(cacheKey) as T;
if (cachedData != null) {
return cachedData;
}
// The value still did not exist so we now write it in to the cache.
cachedData = GetData();
MemoryCache.Default.Set(cacheKey, cachedData, new CacheItemPolicy(...));
return cachedData;
}
}
解决方案
我不知道如何MemoryCache.Default
实施,或者您是否可以控制它。但总的来说,更喜欢在多线程环境中使用ConcurrentDictionary
over with lock。Dictionary
GetFromCache
会变成
ConcurrentDictionary<string, T> cache = new ConcurrentDictionary<string, T>();
...
cache.GetOrAdd("someKey", (key) =>
{
var data = PullDataFromDatabase(key);
return data;
});
还有两件事需要注意。
到期
您可以定义一个类型,而不是保存T
为字典的值
struct CacheItem<T>
{
public T Item { get; set; }
public DateTime Expiry { get; set; }
}
并将缓存存储为CacheItem
具有定义到期时间的缓存。
cache.GetOrAdd("someKey", (key) =>
{
var data = PullDataFromDatabase(key);
return new CacheItem<T>() { Item = data, Expiry = DateTime.UtcNow.Add(TimeSpan.FromHours(1)) };
});
现在您可以在异步线程中实现过期。
Timer expirationTimer = new Timer(ExpireCache, null, 60000, 60000);
...
void ExpireCache(object state)
{
var needToExpire = cache.Where(c => DateTime.UtcNow >= c.Value.Expiry).Select(c => c.Key);
foreach (var key in needToExpire)
{
cache.TryRemove(key, out CacheItem<T> _);
}
}
每分钟一次,您搜索所有需要过期的缓存条目,并将它们删除。
“锁定”
使用ConcurrentDictionary
保证同时读/写不会损坏字典或引发异常。但是,您仍然可能会遇到两个同时读取导致您从数据库中获取数据两次的情况。
解决此问题的一个巧妙技巧是将字典的值包装为Lazy
ConcurrentDictionary<string, Lazy<CacheItem<T>>> cache = new ConcurrentDictionary<string, Lazy<CacheItem<T>>>();
...
var data = cache.GetOrData("someKey", key => new Lazy<CacheItem<T>>(() =>
{
var data = PullDataFromDatabase(key);
return new CacheItem<T>() { Item = data, Expiry = DateTime.UtcNow.Add(TimeSpan.FromHours(1)) };
})).Value;
解释
在同时请求的情况下,GetOrAdd
您最终可能会多次调用“如果不在缓存中则从数据库获取”委托。但是,GetOrAdd
最终将只使用委托返回的值之一,并且通过返回 a Lazy
,您保证只有一个Lazy
会被调用。
推荐阅读
- java - 每次吃东西我都需要增加蛇的速度
- lambda - 从匿名函数汇编脚本访问全局变量
- mercurial - 等效于 mercurial 的“git bisect run”?
- javascript - 将日期格式转换为时间戳
- python - 添加共享库时 scons 编译失败
- javascript - 如何在打字稿或javascript中将用户定义的日期时间转换为毫秒?
- bash - 如何递归退出嵌套shell?
- anypoint-studio - 在 Mulesoft 4 中的单个流中是否可以有两个连续的请求?
- excel - 生成随机字符串,包括特殊字符
- c# - 尽管输入 DataReader.Read() 代码块,SqlDataReader 仍返回“枚举未产生结果”