【发布时间】:2015-11-29 16:09:58
【问题描述】:
搜索结果缓存的工作原理
当用户输入query 进行搜索时:
- 查询被拆分成一个标记数组
- 为这个令牌数组创建一个唯一的散列(按字母顺序排列令牌,然后按 MD5 排序)。这是搜索的唯一标识。
- 根据哈希检查缓存结果
- 如果缓存不存在,则使用哈希将结果保存到缓存
我正在尝试解决的问题
如果用户执行的搜索需要 10 秒,并且他们不耐烦地刷新页面,我们不希望它再次开始查询。这应该被锁定。
但是,如果正在运行昂贵的查询,我们不希望阻止其他执行成本较低的搜索的用户。
要解决这个问题,我需要多个锁。
实施
这就是我目前实现它的方式:
private static readonly object MasterManualSearchLock = new object();
private static readonly Dictionary<string, object> ManualSearchLocks = new Dictionary<string, object>();
/// <summary>
/// Search the manual
/// </summary>
public static SearchResponse DoSearch(string query, Manual forManual)
{
var tokens = Search.Functions.TokeniseSearchQuery(query);
var tokenHash = Search.Functions.GetUniqueHashOfTokens(tokens);
var cacheIndex = Settings.CachePrefix + "SavedManualSearch_" + tokenHash;
var context = HttpContext.Current;
if (context.Cache[cacheIndex] == null)
{
// Create lock if it doesn't exist
if (!ManualSearchLocks.ContainsKey(tokenHash))
{
lock (MasterManualSearchLock)
{
if (!ManualSearchLocks.ContainsKey(tokenHash))
{
ManualSearchLocks.Add(tokenHash, new object());
}
}
}
lock (ManualSearchLocks[tokenHash])
{
if (context.Cache[cacheIndex] == null)
{
var searchResponse = new SearchResponse(tokens, forManual, query);
context.Cache.Add(cacheIndex, searchResponse, null, DateTime.Now.AddMinutes(Settings.Search.SearchResultsAbsoluteTimeoutMins), Cache.NoSlidingExpiration, CacheItemPriority.BelowNormal, null);
}
ManualSearchLocks.Remove(tokenHash);
}
}
return (SearchResponse)context.Cache[cacheIndex];
}
问题
- 这是一个合理的实现吗?
- 这个线程安全吗?
- 在锁本身中包括移除锁是否可以?
【问题讨论】:
-
有一个小窗口
lock (ManualSearchLocks[tokenHash])可能会失败,因为另一个线程刚刚为相同的哈希执行了ManualSearchLocks.Remove(tokenHash);(在第一个线程“保证”它在字典中之后)。 -
@EricJ。拆除锁是否应该由主锁锁定?另外,我是否应该为锁字典使用不同的数据类型,也许是
ConcurrentDictionary? -
这不会改变任何事情,因为在我的场景中你已经释放了你当前的
lock (MasterManualSearchLock),但是如果你将整段代码包装在同一个锁中,你将有效地序列化你的代码。 -
System.Runtime.Caching.MemoryCache -
查看@Eser 的建议。 MemoryCache 已经处理了多线程访问缓存的问题,提供灵活的缓存失效等。
标签: c# asp.net search thread-safety