C#静态类状态丢失:服务器环境feed_expiration_date每次请求为null
问题描述
本地与服务器均配置8进程Web Garden,本地环境下静态类CacheManager的feed_expiration_date可正常维持状态;但服务器环境中,该DateTime?变量每次请求都会变为null。同一进程ID(如PID:1234)在有效期内的重复请求,本地可保留之前的变量值,服务器却无法实现,需要技术支持。
相关代码
public static class CacheManager { public static ClientData ClientDataCache { get { return GetClientDataCacheByID(HostInstanceID); } } public static ClientData GetClientDataCacheByID(int instance_id) { try { ClientData data = null; if (instance_id != 0) data = client_cache_data_dictionary.ContainsKey(instance_id) ? client_cache_data_dictionary[instance_id] : null; if (data == null || data.GlobalSettingsCache == null || DateTime.UtcNow > data.date_cached.AddHours(1)) return GenerateClientData(); return data; } catch (Exception ex) { return null; } } private static ClientData GenerateClientData() { lock (InstanceLocker) { ClientData data = null; int instance_id = HostInstanceID; if (instance_id != 0) data = client_cache_data_dictionary.ContainsKey(instance_id) ? client_cache_data_dictionary[instance_id] : null; if (data == null || data.GlobalSettingsCache == null || DateTime.UtcNow > data.date_cached.AddHours(1)) { data = new ClientData(true); if (data != null && data.InstanceID > 0) { int id = data.InstanceID; HostInstanceID = id; client_cache_data_dictionary[id] = data; // To prevent race conditions, this has to happen prior to client_data_cache_lock_dictionary[id] = new object(); client_data_cache_lock_dictionary[id] = new object(); // This has to happen 2nd, not first if (Portals.Utils.IsEC2) { try { if (!global_cache_mgr_init) { GlobalCacheManager.GlobalCacheManager.Initialize(GlobalCacheManager.Enumerations.SystemLocations.AdminPortal, !Portals.Utils.IsEC2 ? "us-west-2" : null); global_cache_mgr_init = true; GlobalCacheManager.GlobalCacheManager.Start(); } } catch (Exception ex) { Emails.SendAdminEmail("Error Init/Start GCM on ClientCache.cs", ex.StackTrace); } } } Portals.Utils.LogErrorFeed("ReadGSFromDB","true PID: " + System.Diagnostics.Process.GetCurrentProcess().Id); } else { Portals.Utils.LogErrorFeed("ReadGSFromCache", "true PID: " + System.Diagnostics.Process.GetCurrentProcess().Id); } return data; } } public class ClientData { public ClientData(bool initdata = false) { if (initdata) IntializeClientData(); } private List<SurveysAndFeaturesFeedResult> surveyAndFeaturesFeeds = null; public DateTime date_cached { get; set; } public DateTime? feed_expiration_date { get;set; } private object surveys_features_generate_locker = new object(); public void GenerateSurveysAndFeaturesCache(bool force_refresh = false) { if(this.InstanceID != 0) { lock(surveys_features_generate_locker) { if (this.surveyAndFeaturesFeeds == null || force_refresh) { this.surveyAndFeaturesFeeds = CakeFeed.GetSurveysAndFeaturesFeedResult(); this.feed_expiration_date = DateTime.UtcNow.AddHours(this.global_settings.CakeFeedCacheDurationHoursCKM); } } if(force_refresh && this.surveyAndFeaturesFeeds != null) { Portals.Utils.RefreshPortals(); } } } public List<SurveysAndFeaturesFeedResult> SurveysAndFeaturesFeed { get { if(this.global_settings.EnablePresentIcon && (surveyAndFeaturesFeeds == null || feed_expiration_date == null || feed_expiration_date < DateTime.UtcNow)) { GenerateSurveysAndFeaturesCache(); } return (surveyAndFeaturesFeeds == null) ? new List<SurveysAndFeaturesFeedResult>() : surveyAndFeaturesFeeds.ToList(); } } } }
调用方式
在handler.ashx中调用:
CacheManager.ClientDataCache.SurveysAndFeaturesFeed.OrderByDescending(x=>x.PublishDate).ToList();
问题分析与解决方案
核心原因
Web Garden下每个工作进程是独立的应用域,但你遇到的是同一进程内状态丢失,说明进程内的ClientData实例被重复重建,导致feed_expiration_date被重置为null。结合代码排查,问题点集中在:
date_cached未明确初始化,默认值为DateTime.MinValue,导致GetClientDataCacheByID每次都判定缓存失效,重建ClientData。HostInstanceID在进程内未正确持久化,导致每次请求都从字典取不到缓存,生成新实例。SurveysAndFeaturesFeed的getter未先校验InstanceID有效性,若InstanceID为0,GenerateSurveysAndFeaturesCache不会执行,feed_expiration_date一直为null,陷入循环重建。
修复步骤
- 初始化
date_cached:在ClientData.IntializeClientData方法中添加date_cached = DateTime.UtcNow;,避免默认值导致缓存失效误判。 - 双重检查锁定优化缓存逻辑:修改
GetClientDataCacheByID,在锁内二次校验缓存,避免多线程重复创建实例:public static ClientData GetClientDataCacheByID(int instance_id) { try { ClientData data = null; if (instance_id != 0) data = client_cache_data_dictionary.TryGetValue(instance_id, out var cachedData) ? cachedData : null; if (data == null || data.GlobalSettingsCache == null || DateTime.UtcNow > data.date_cached.AddHours(1)) { lock (InstanceLocker) { if (instance_id != 0) client_cache_data_dictionary.TryGetValue(instance_id, out data); if (data == null || data.GlobalSettingsCache == null || DateTime.UtcNow > data.date_cached.AddHours(1)) { data = GenerateClientData(); } } } return data; } catch (Exception ex) { return null; } } - 完善缓存触发条件:修改
SurveysAndFeaturesFeed的getter,先校验InstanceID有效性:public List<SurveysAndFeaturesFeedResult> SurveysAndFeaturesFeed { get { if(this.InstanceID != 0 && this.global_settings.EnablePresentIcon && (surveyAndFeaturesFeeds == null || feed_expiration_date == null || feed_expiration_date < DateTime.UtcNow)) { GenerateSurveysAndFeaturesCache(); } return surveyAndFeaturesFeeds?.ToList() ?? new List<SurveysAndFeaturesFeedResult>(); } } - 日志排查:在
ClientData构造函数和GenerateSurveysAndFeaturesCache中添加日志,记录InstanceID、feed_expiration_date值及进程ID,确认同一进程内是否真的在重复创建实例。
内容的提问来源于stack exchange,提问作者Asad
相关产品推荐
相关产品推荐

