public void TestSingleLock() { var resources = Mocker.MockResourcesWithThree(); using (var @lock = _lockManager.Lock("Test", resources, TimeSpan.FromSeconds(30))) { Assert.True(@lock.IsAcquired); } }
/// <summary> /// CacheAsideAsync /// </summary> /// <param name="cacheItem"></param> /// <param name="dbRetrieve"></param> /// <param name="cache"></param> /// <param name="memoryLockManager"></param> /// <param name="database"></param> /// <param name="logger"></param> /// <returns></returns> /// <exception cref="CacheException"></exception> /// <exception cref="RepositoryException"></exception> public static async Task <TResult?> CacheAsideAsync <TResult>( CachedItem <TResult> cacheItem, Func <IDatabaseReader, Task <TResult> > dbRetrieve, ICache cache, IMemoryLockManager memoryLockManager, IDatabase database, ILogger logger) where TResult : class { //Cache First TResult?result = await cacheItem.GetFromAsync(cache).ConfigureAwait(false); if (result != null) { return(result); } using var @lock = memoryLockManager.Lock(cacheItem.ResourceType, cacheItem.CacheKey, Consts.OccupiedTime, Consts.PatienceTime); if (@lock.IsAcquired) { //Double Check result = await cacheItem.GetFromAsync(cache).ConfigureAwait(false); if (result != null) { return(result); } TResult dbRt = await dbRetrieve(database).ConfigureAwait(false); UtcNowTicks now = TimeUtil.UtcNowTicks; // 如果TResult是集合类型,可能会存入空集合。而在EntityCache中是不会存入空集合的。 //这样设计是合理的,因为EntityCache是按Entity角度,存入的Entity会复用,就像一个KVStore一样,而CachedItem纯粹是一个查询结果,不思考查询结果的内容。 if (dbRt != null) { UpdateCache(cacheItem.Value(dbRt).Timestamp(now), cache); logger.LogInformation($"缓存 Missed. Entity:{cacheItem.GetType().Name}, CacheKey:{cacheItem.CacheKey}"); } else { logger.LogInformation($"查询到空值. Entity:{cacheItem.GetType().Name}, CacheKey:{cacheItem.CacheKey}"); } return(dbRt); } else { logger.LogCritical($"锁未能占用. Entity:{cacheItem.GetType().Name}, CacheKey:{cacheItem.CacheKey}, Lock Status:{@lock.Status}"); return(await dbRetrieve(database).ConfigureAwait(false)); } }
public static async Task <IEnumerable <TEntity> > CacheAsideAsync <TEntity>(string dimensionKeyName, IEnumerable dimensionKeyValues, Func <IDatabaseReader, Task <IEnumerable <TEntity> > > dbRetrieve, IDatabase database, ICache cache, IMemoryLockManager memoryLockManager, ILogger logger) where TEntity : Entity, new() { if (!ICache.IsEntityEnabled <TEntity>()) { return(await dbRetrieve(database).ConfigureAwait(false)); } try { (IEnumerable <TEntity>?cachedEntities, bool allExists) = await cache.GetEntitiesAsync <TEntity>(dimensionKeyName, dimensionKeyValues).ConfigureAwait(false); if (allExists) { logger.LogDebug("Cache中全部存在,返回. Entity: {EntityType}", typeof(TEntity).Name); return(cachedEntities !); } } catch (Exception ex) { //有可能实体定义发生改变,导致缓存读取出错 logger.LogError2(ex, $"读取缓存出错,缓存可能已经被删除,继续读取数据库,dimensionKeyName:{dimensionKeyName}, dimensionKeyValues:{SerializeUtil.ToJson(dimensionKeyValues)}"); } //常规做法是先获取锁(参看历史)。 //但如果仅从当前dimension来锁的话,有可能被别人从其他dimension操作同一个entity, //所以这里改变常规做法,先做database retrieve //以上是针对无version版本cache的。现在不用担心从其他dimension操作同一个entity了,cache会自带version来判断。 //而且如果刚开始很多请求直接打到数据库上,数据库撑不住,还是得加锁。 //但可以考虑加单机本版的锁就可,这个锁主要为了降低数据库压力,不再是为了数据一致性(带version的cache自己解决)。 //所以可以使用单机版本的锁即可。一个主机同时放一个db请求,还是没问题的。 List <string> resources = new List <string>(); foreach (object dimensionKeyValue in dimensionKeyValues) { resources.Add(dimensionKeyName + dimensionKeyValue.ToString()); } using var @lock = memoryLockManager.Lock(typeof(TEntity).Name, resources, Consts.OccupiedTime, Consts.PatienceTime); if (@lock.IsAcquired) { try { //Double check (IEnumerable <TEntity>?cachedEntities, bool allExists) = await cache.GetEntitiesAsync <TEntity>(dimensionKeyName, dimensionKeyValues).ConfigureAwait(false); if (allExists) { logger.LogDebug("Cache中全部存在,返回. Entity: {EntityType}", typeof(TEntity).Name); return(cachedEntities !); } } catch (Exception ex) { //有可能实体定义发生改变,导致缓存读取出错 logger.LogError2(ex, $"!!!这里是读取缓存的DoubleCheck,这里不应该出现,缓存可能已经被删除,继续读取数据库,dimensionKeyName:{dimensionKeyName}, dimensionKeyValues:{SerializeUtil.ToJson(dimensionKeyValues)}"); } IEnumerable <TEntity> entities = await dbRetrieve(database).ConfigureAwait(false); if (entities.IsNotNullOrEmpty()) { UpdateCache(entities, cache); logger.LogInformation($"缓存 Missed. Entity:{typeof(TEntity).Name}, DimensionKeyName:{dimensionKeyName}, DimensionKeyValues:{dimensionKeyValues.ToJoinedString(",")}"); } else { logger.LogInformation($"查询到空值. Entity:{typeof(TEntity).Name}, DimensionKeyName:{dimensionKeyName}, DimensionKeyValues:{dimensionKeyValues.ToJoinedString(",")}"); } return(entities); } else { logger.LogError($"锁未能占用. Entity:{typeof(TEntity).Name}, dimensionKeyName:{dimensionKeyName},dimensionKeyValues:{dimensionKeyValues.ToJoinedString(",")}, Lock Status:{@lock.Status}"); return(await dbRetrieve(database).ConfigureAwait(false)); } }