コード例 #1
0
ファイル: DistributeCache.cs プロジェクト: Kjubo/xms.core
			public LocalCacheItemCallbackWrapper(LocalCacheItem item, string key, Func<object, object> callback, object callBackState, int timeToLiveInSeconds, params string[] tags)
			{
				this.Item = item;
				this.Key = key;

				this.Callback = callback;
				this.CallBackState = callBackState;

				this.TTL = TimeSpan.FromSeconds(timeToLiveInSeconds);

				this.Tags = tags;
			}
コード例 #2
0
ファイル: DistributeCache.cs プロジェクト: Kjubo/xms.core
		public object GetAndSetItem(string key, Func<object, object> callback, object callBackState, params string[] tags)
		{
			DataCacheItem cachedItem = this.GetItemInternal(key);

			LocalCacheItem localCacheItem;

			// 缓存项不存在时立即添加新的缓存项并返回
			if (cachedItem == null)
			{
				object cacheItemLock = null;

				lock (cacheItemLocks)
				{
					if (!cacheItemLocks.ContainsKey(key))
					{
						cacheItemLock = new object();

						cacheItemLocks[key] = cacheItemLock;
					}
					else
					{
						cacheItemLock = cacheItemLocks[key];
					}
				}

				object value = null;

				lock (cacheItemLock)
				{
					cachedItem = this.GetItemInternal(key);

					if (cachedItem == null)
					{
						value = callback(callBackState);

						if (value != null)
						{
							localCacheItem = new LocalCacheItem();
							localCacheItem.NextUpdateTime = DateTime.Now.AddSeconds(this.asyncUpdateInterval); // 下次更新时间
							localCacheItem.IsUpdating = false;
							// 缓存项在缓存服务器中的生存时间为:异步更新间隔+5分钟,这足够保证缓存项能够及时成功更新
							// 同时,可以避免添加当前缓存项的客户端应用终止时(客户端应用中保存的 cacheItemVersions 失效,其它客户端因为仅读取该缓存项而没有存储其版本,永远不会更新该缓存项)
							// 缓存项将可能在很长时间(由传入的 timeToLive 值决定)内不再更新的问题
							localCacheItem.Version = this.SetItemInternal(key, value, TimeSpan.FromSeconds(this.asyncUpdateInterval + this.asyncUpdateAdditionalLiveTime), tags);


							this.lock4cacheItemVersions.EnterWriteLock();
							try
							{
								this.cacheItemVersions[key] = localCacheItem;
							}
							finally
							{
								this.lock4cacheItemVersions.ExitWriteLock();
							}
						}
					}
					else
					{
						value = cachedItem.Value;
					}
				}

				lock (cacheItemLocks)
				{
					cacheItemLocks.Remove(key);
				}

				return value;
			}

			this.lock4cacheItemVersions.EnterReadLock();
			try
			{
				localCacheItem = this.cacheItemVersions.ContainsKey(key) ? this.cacheItemVersions[key] : null;
			}
			finally
			{
				this.lock4cacheItemVersions.ExitReadLock();
			}

			// 首先不是处于更新过程中且时间上判断应更新缓存项,使用双重检查锁定机制并通过任务并行库异步更新缓存项
			if (localCacheItem != null)
			{
				//如果缓存服务器返回的缓存项的版本与本地存储的版本相同的话,说明服务器的缓存项是在本地设置的
				if (localCacheItem.Version == cachedItem.Version)
				{
					if (!localCacheItem.IsUpdating && localCacheItem.NextUpdateTime < DateTime.Now)
					{
						lock (localCacheItem) // 仅锁定当前缓存项,尽可能的避免阻塞对其它缓存项的访问
						{
							if (!localCacheItem.IsUpdating && localCacheItem.NextUpdateTime < DateTime.Now)
							{
								localCacheItem.IsUpdating = true;

								System.Threading.Tasks.Task.Factory.StartNew(this.UpdateCacheItem, new LocalCacheItemCallbackWrapper(localCacheItem, key, callback, callBackState, this.asyncUpdateInterval + this.asyncUpdateAdditionalLiveTime, tags));
							}
						}
					}
				}
				else//缓存项已经被其它程序更新,移除 localCacheItem,以便仅在其它程序中维护它
				{
					// 以下两种情况会造成 localCacheItem 的版本与 cachedItem 的版本不相等
					//	1. 当前应用的其它运行实例或其它应用主动修改了同一键值的缓存
					//		这种情况下,localCacheItem.NextUpdateTime 和 localCacheItem.Version 之后将永远不会再被修改,因此,只要在超过 localCacheItem.NextUpdateTime 时从 cacheItemVersions 中移除当前键值对应的 localCacheItem 即可。
					//	2. 在从 cacheItemVersions 中获取到 localCacheItem 后并且执行到 localCacheItem.Version == cachedItem.Version 进行比较的过程中,恰好有另外一个线程命中
					//		这种情况下,localCacheItem.NextUpdateTime 通常大于 当前时间(因为 UpdateCacheItem 中更新 localCacheItem.Version 时会先更新其 NextUpdateTime 属性),但可能存在以下例外:
					//			对于更新间隔时间很小的情况(比如 0.1 秒),有可能因为在该间隔内从取到 localCacheItem 开始未执行到这里而造成 localCacheItem.NextUpdateTime 小于 当前时间。
					// 综上,假设程序在 1 分钟之内从取到 localCacheItem 开始一定能执行到这里(通常一定能满足),则只需要判断 localCacheItem.NextUpdateTime + 1 分钟 小于 当前时间 时移除 localCacheItem 即可。
					if (localCacheItem.NextUpdateTime.AddMinutes(1) < DateTime.Now) 
					{
						this.lock4cacheItemVersions.EnterWriteLock();
						try
						{
							if (this.cacheItemVersions[key] == localCacheItem)
							{
								this.cacheItemVersions.Remove(key);
							}
						}
						finally
						{
							this.lock4cacheItemVersions.ExitWriteLock();
						}
					}
				}
			}

			// 在异步更新缓存项之前立即返回当前缓存项的值。
			return cachedItem.Value;
		}
コード例 #3
0
        /// <summary>
        /// Checks the local cache timestamp against the distributed cache timestamp if the local timestamp
        /// is blank or old than the distributed cache's, it updates it with a fresh copy from the database,
        /// it also updates it if the timestamp in the distributed cache is blank or invalid
        /// </summary>
        private void EnsureFreshnessOfCaches(params string[] cultureNames)
        {
            // Determine which caches represent a stale caches and need refreshing
            List <CacheInfo> staleCaches = new List <CacheInfo>();

            foreach (var cultureName in cultureNames)
            {
                // Retrieve the timestamps from the distributed cache
                var cacheKey    = CacheKey(cultureName);
                var distVersion = _distributedCache.GetString(cacheKey);

                // If the distributed cache is blank or invalid, set it to a new version
                if (distVersion == null)
                {
                    // Either the cache was flushed, or has been illegally tampered with,
                    // simply reset the cache to NOW, to invalidate all local caches
                    distVersion = Guid.NewGuid().ToString();
                    _distributedCache.SetString(cacheKey, distVersion, GetDistributedCacheOptions());

                    // NOTE: This should work fine with concurrency, the worst that could happen is
                    // that if multiple nodes independently find that the distributed cache is blank
                    // then one of the nodes may overwrite the version inserted by the other nodes
                    // and then for those other nodes, the freshly retrieved translations may end up
                    // being fetched once more in the next request, since their local cache will have
                    // a different version
                }

                _localCache.TryGetValue(cacheKey, out LocalCacheItem localCacheItem);

                // If the local cache is blank or outdated, grab a fresh copy from the DB
                if (localCacheItem == null || localCacheItem.Version != distVersion)
                {
                    // Remember the version from the distributed cache
                    staleCaches.Add(new CacheInfo
                    {
                        LatestVersion = distVersion,
                        CultureName   = cultureName
                    });
                }
            }

            ////// Efficiently retrieve a fresh list of translations for all stale caches
            Dictionary <string, Dictionary <string, string> > freshTranslations = new Dictionary <string, Dictionary <string, string> >();

            // Get translations from AdminContext
            if (staleCaches.Any())
            {
                var staleCacheKeys = staleCaches.Select(e => e.CultureName);
                using (var scope = _serviceProvider.CreateScope())
                {
                    using (var ctx = scope.ServiceProvider.GetRequiredService <AdminContext>())
                    {
                        var freshTranslationsQuery = from e in ctx.Translations
                                                     where (e.Tier == Constants.Server || e.Tier == Constants.Shared) && staleCacheKeys.Contains(e.CultureId)
                                                     group e by e.CultureId into g
                                                     select g;

                        freshTranslations = freshTranslationsQuery.AsNoTracking().ToDictionary(
                            g => CacheKey(g.Key),
                            g => g.ToDictionary(e => e.Name, e => e.Value));
                    }
                }
            }

            // Update the stale caches with the fresh translations
            foreach (var staleCache in staleCaches)
            {
                // Get the cache key
                var cacheKey = CacheKey(staleCache.CultureName);

                // Prepare the translations in a dictionary, even an empty one if no list came from SQL
                Dictionary <string, string> translations = new Dictionary <string, string>();
                if (freshTranslations.ContainsKey(cacheKey))
                {
                    translations = freshTranslations[cacheKey];
                }

                // Set the local cache
                _localCache[cacheKey] = new LocalCacheItem
                {
                    Version      = staleCache.LatestVersion,
                    Translations = translations
                };
            }
        }