如何在两台Azure实例中实现一致的System.Runtime.Caching内存缓存(不使用Redis)
解决Azure多实例ASP.NET MVC应用内存缓存不一致问题(无需Redis)
针对Azure上多实例部署的ASP.NET MVC应用,使用System.Runtime.Caching内存缓存时出现的实例间缓存不一致问题,以下是几种无需Redis的可行解决方案:
1. 启用粘性会话(Affinity Cookie)
原理
让同一用户的所有请求始终路由到同一应用实例,确保用户只会访问该实例的本地缓存,避免跨实例的缓存不一致问题。
配置方式
在Azure App Service的配置>常规设置中,找到"粘性会话"选项并启用即可,无需修改代码。
优缺点
- ✅ 配置简单,零代码改动
- ❌ 实例故障时用户会切换到其他实例,导致缓存丢失;可能造成实例负载不均;无法解决跨用户的缓存一致性问题
2. 基于Azure存储的缓存同步(Blob/Table Storage)
思路
将缓存的源数据存储在Azure Blob或Table Storage中,每个实例的本地内存缓存从存储加载,并通过自定义变更监视器监听存储的变化,自动更新本地缓存。
实现要点
- 封装统一的缓存操作类,读取缓存时优先查本地内存缓存,不存在则从Azure存储加载并写入本地
- 自定义
ChangeMonitor监听Azure存储的文件/数据变更,当源数据更新时,自动失效本地缓存
简化代码示例
// 自定义Blob变更监视器 public class BlobChangeMonitor : ChangeMonitor { private readonly CloudBlob _targetBlob; private DateTimeOffset _lastModifiedTime; private Timer _monitorTimer; public BlobChangeMonitor(CloudBlob blob) { _targetBlob = blob; _lastModifiedTime = blob.Properties.LastModified.Value; StartMonitoring(); InitializationComplete(); } private void StartMonitoring() { // 每30秒检查一次Blob修改时间 _monitorTimer = new Timer(async _ => { await _targetBlob.FetchAttributesAsync(); if (_targetBlob.Properties.LastModified.Value != _lastModifiedTime) { _lastModifiedTime = _targetBlob.Properties.LastModified.Value; OnChanged(null); // 触发缓存失效 } }, null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); } protected override void Dispose(bool disposing) { _monitorTimer?.Dispose(); } } // 使用缓存的示例方法 public object GetCachedData(string cacheKey) { var memoryCache = MemoryCache.Default; var cachedItem = memoryCache.Get(cacheKey); if (cachedItem == null) { // 从Azure Blob加载源数据 var blob = GetCloudBlobReference(cacheKey); cachedItem = DeserializeDataFromBlob(blob); // 设置缓存依赖,监听Blob变化 var cachePolicy = new CacheItemPolicy { ChangeMonitors = { new BlobChangeMonitor(blob) } }; memoryCache.Set(cacheKey, cachedItem, cachePolicy); } return cachedItem; }
优缺点
- ✅ 依赖Azure存储的高可用性,数据一致性较好
- ❌ 缓存更新存在一定延迟;增加存储成本和网络开销
3. 使用SQL Server + SqlCacheDependency
思路
将缓存源数据存储在Azure SQL Server中,利用SqlCacheDependency监听数据库表的变更,当表数据更新时自动失效本地内存缓存,各实例重新加载最新数据。
配置步骤
- 启用SQL Server的Service Broker(需在数据库执行:
ALTER DATABASE [YourDBName] SET ENABLE_BROKER;) - 在ASP.NET项目中配置
SqlCacheDependency,启用目标表的变更通知
简化代码示例
// 初始化SQL缓存依赖(可在Global.asax中执行) string connectionString = ConfigurationManager.ConnectionStrings["CacheDB"].ConnectionString; SqlCacheDependencyAdmin.EnableNotifications(connectionString); SqlCacheDependencyAdmin.EnableTableForNotifications(connectionString, "CacheDataTable"); // 获取缓存数据 public object GetSqlBackedCachedData(string cacheKey) { var memoryCache = MemoryCache.Default; var cachedItem = memoryCache.Get(cacheKey); if (cachedItem == null) { // 从SQL Server加载数据 cachedItem = LoadDataFromSqlServer(); // 设置SQL缓存依赖 var cachePolicy = new CacheItemPolicy { ChangeMonitors = { new SqlChangeMonitor(new SqlCacheDependency("CacheDB", "CacheDataTable")) } }; memoryCache.Set(cacheKey, cachedItem, cachePolicy); } return cachedItem; }
优缺点
- ✅ 数据一致性高,适合已有SQL Server的场景
- ❌ 需要配置SQL Server Service Broker,可能增加数据库负载
4. 基于Azure Service Bus的缓存失效通知
思路
当任意实例更新缓存源数据时,通过Azure Service Bus发送缓存失效消息,其他实例订阅消息后立即清除对应本地缓存项,确保所有实例的缓存同步失效。
实现要点
- 每个实例订阅Service Bus的主题/队列
- 更新源数据后,发送包含缓存键的失效消息到Service Bus
- 实例收到消息后,调用
MemoryCache.Default.Remove()清除对应缓存
简化代码示例
// 初始化Service Bus订阅(实例启动时执行) var subscriptionClient = new SubscriptionClient(ServiceBusConnectionString, "CacheInvalidationTopic", "InstanceSubscription"); subscriptionClient.RegisterMessageHandler(async (message, cancellationToken) => { var cacheKey = Encoding.UTF8.GetString(message.Body); MemoryCache.Default.Remove(cacheKey); await subscriptionClient.CompleteAsync(message.SystemProperties.LockToken); }, new MessageHandlerOptions(ExceptionReceivedHandler) { MaxConcurrentCalls = 1 }); // 发送缓存失效消息(更新源数据时执行) var topicClient = new TopicClient(ServiceBusConnectionString, "CacheInvalidationTopic"); var invalidationMessage = new Message(Encoding.UTF8.GetBytes("TargetCacheKey")); await topicClient.SendAsync(invalidationMessage); // 异常处理方法 private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs args) { // 处理消息接收异常 return Task.CompletedTask; }
优缺点
- ✅ 缓存失效实时性好,能快速同步跨实例缓存状态
- ❌ 需要额外配置Service Bus,增加成本和系统复杂度
方案选择建议
- 若能接受用户固定实例的限制,优先选粘性会话,配置成本最低
- 已有Azure SQL Server且要求高一致性,选SQL Server + SqlCacheDependency
- 需要实时跨实例缓存同步,选Azure Service Bus失效通知
- 以Azure存储作为数据存储载体,选Azure存储+自定义ChangeMonitor
内容的提问来源于stack exchange,提问作者Madhu Chepuroju
相关产品推荐
相关产品推荐

