/// <summary> /// SetAsync /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="utcTicks"></param> /// <param name="options"></param> /// <param name="token"></param> /// <returns></returns> /// <exception cref="CacheException"></exception> public async Task <bool> SetAsync(string key, byte[] value, UtcNowTicks utcTicks, DistributedCacheEntryOptions options, CancellationToken token = default) { token.ThrowIfCancellationRequested(); IDatabase database = await GetDefaultDatabaseAsync().ConfigureAwait(false); if (options.AbsoluteExpirationRelativeToNow.HasValue) { options.AbsoluteExpiration = TimeUtil.UtcNow + options.AbsoluteExpirationRelativeToNow; } long?absoluteExpireUnixSeconds = options.AbsoluteExpiration?.ToUnixTimeSeconds(); long?slideSeconds = (long?)(options.SlidingExpiration?.TotalSeconds); try { RedisResult redisResult = await database.ScriptEvaluateAsync(GetDefaultLoadLuas().LoadedSetWithTimestampLua, new RedisKey[] { GetRealKey("", key) }, new RedisValue[] { absoluteExpireUnixSeconds ?? -1, slideSeconds ?? -1, GetInitialExpireSeconds(absoluteExpireUnixSeconds, slideSeconds) ?? -1, value, utcTicks.Ticks }).ConfigureAwait(false); int rt = (int)redisResult; if (rt == 1) { return(true); } else if (rt == 8) { _logger.LogWarning("检测到,Cache Invalidation Concurrency冲突,已被阻止. {key}, {Timestamp}", key, utcTicks); } else if (rt == 9) { _logger.LogWarning("检测到,Cache Update Concurrency冲突,已被阻止. {key}, {Timestamp}", key, utcTicks); } return(false); } catch (RedisServerException ex) when(ex.Message.StartsWith("NOSCRIPT", StringComparison.InvariantCulture)) { _logger.LogError(ex, "NOSCRIPT, will try again."); InitLoadedLuas(); return(await SetAsync(key, value, utcTicks, options, token).ConfigureAwait(false)); } catch (Exception ex) { _logger.LogError(ex, "分析这个"); throw Exceptions.Unkown(key, null, ex); } }
/// <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)); } }
/// <summary> /// 返回是否找到了 /// </summary> /// <param name="key"></param> /// <param name="timestampInUnixMilliseconds"></param> /// <param name="token"></param> /// <returns></returns> /// <exception cref="CacheException"></exception> public async Task <bool> RemoveAsync(string key, UtcNowTicks utcTicks, CancellationToken token = default) { if (key == null) { throw new ArgumentNullException(nameof(key)); } token.ThrowIfCancellationRequested(); IDatabase database = await GetDefaultDatabaseAsync().ConfigureAwait(false); try { RedisResult redisResult = await database.ScriptEvaluateAsync( GetDefaultLoadLuas().LoadedRemoveWithTimestampLua, new RedisKey[] { GetRealKey("", key) }, new RedisValue[] { utcTicks.Ticks, _invalidationVersionExpirySeconds }).ConfigureAwait(false); return((int)redisResult == 1); } catch (RedisServerException ex) when(ex.Message.StartsWith("NOSCRIPT", StringComparison.InvariantCulture)) { _logger.LogError(ex, "NOSCRIPT, will try again."); InitLoadedLuas(); return(await RemoveAsync(key, utcTicks, token).ConfigureAwait(false)); } catch (Exception ex) { _logger.LogError(ex, "分析这个"); throw Exceptions.Unkown(key, null, ex); } }
public async Task CacheTimestamp_Timestamp_TestAsync(int?absoluteSecondsRelativeToNow, int?slidingSeconds) { DistributedCacheEntryOptions entryOptions = new DistributedCacheEntryOptions(); entryOptions.AbsoluteExpirationRelativeToNow = absoluteSecondsRelativeToNow == null ? null : (TimeSpan?)TimeSpan.FromSeconds(absoluteSecondsRelativeToNow.Value); entryOptions.SlidingExpiration = slidingSeconds == null ? null : (TimeSpan?)TimeSpan.FromSeconds(slidingSeconds.Value); IDatabase database = _redisConnection.GetDatabase(_databaseNumber); Book book = Mocker.MockOne(); await AddToDatabaeAsync(new Book[] { book }).ConfigureAwait(false); UtcNowTicks utcNowTicks = TimeUtil.UtcNowTicks; UtcNowTicks utcNowTicks2 = TimeUtil.UtcNowTicks; UtcNowTicks utcNowTicks3 = TimeUtil.UtcNowTicks; utcNowTicks2.Ticks -= 10000; utcNowTicks3.Ticks += 10000; string oldName = book.Name; await _cache.SetAsync(nameof(Book) + book.Id.ToString(), book, utcNowTicks, entryOptions).ConfigureAwait(false); book.Name += "22222"; await _cache.SetAsync(nameof(Book) + book.Id.ToString(), book, utcNowTicks2, entryOptions).ConfigureAwait(false); Book cached = await _cache.GetAsync <Book>(nameof(Book) + book.Id.ToString()); Assert.True(cached.Name == oldName); await _cache.SetAsync(nameof(Book) + book.Id.ToString(), book, utcNowTicks3, entryOptions).ConfigureAwait(false); Book cached2 = await _cache.GetAsync <Book>(nameof(Book) + book.Id.ToString()); Assert.True(cached2.Name == book.Name); }
public CachedItem <TResult> Timestamp(UtcNowTicks utcTicks) { UtcTikcs = utcTicks; return(this); }
/// <summary> /// SetStringAsync /// </summary> /// <param name="cache"></param> /// <param name="key"></param> /// <param name="value"></param> /// <param name="utcTicks"></param> /// <param name="options"></param> /// <param name="token"></param> /// <returns></returns> /// <exception cref="CacheException"></exception> public static async Task SetStringAsync(this ICache cache, string key, string value, UtcNowTicks utcTicks, DistributedCacheEntryOptions options, CancellationToken token = default) { try { byte[] bytes = await SerializeUtil.PackAsync(value).ConfigureAwait(false); await cache.SetAsync(key, bytes, utcTicks, options, token).ConfigureAwait(false); } catch (Exception ex) when(ex is not CacheException) { throw Exceptions.Unkown(key: key, value: value, innerException: ex); } }
/// <summary> /// SetIntAsync /// </summary> /// <param name="cache"></param> /// <param name="key"></param> /// <param name="value"></param> /// <param name="utcTicks"></param> /// <param name="options"></param> /// <param name="token"></param> /// <returns></returns> /// <exception cref="CacheException"></exception> public static Task SetIntAsync(this ICache cache, string key, int value, UtcNowTicks utcTicks, DistributedCacheEntryOptions options, CancellationToken token = default) { return(cache.SetStringAsync(key, value.ToString(GlobalSettings.Culture), utcTicks, options, token)); }