Created
June 27, 2010 00:16
-
-
Save woloski/454485 to your computer and use it in GitHub Desktop.
Invalidate ASP.NET Cache using a Windows Azure queue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Southworks.WindowsAzure | |
{ | |
using System; | |
using System.Web.Caching; | |
/// <summary> | |
/// Represents a cache dependency that uses Windows Azure queues to detect if an item in cache has changed | |
/// </summary> | |
/// <remarks>Sending a message a certain queue (specified using the <see cref="CloudQueueCacheDependencyMonitor" />) will invalidate the cache item </remarks> | |
/// <example> | |
/// The following code adds a dependency on a cache item in ASP.NET | |
/// <code> | |
/// Cache.Insert("test", DateTime.Now, new CloudQueueCacheDependency("test")); | |
/// </code> | |
/// The following code that sends a message to the queue "invalidatecache-roleinsanceid" will invalidate the cache item "cache-item-key" | |
/// <code> | |
/// var queue = GetQueue("invalidatecache-roleinsanceid"); | |
/// queue.AddMessage(new CloudQueueMessage("test")); | |
/// </code> | |
/// </example> | |
public class CloudQueueCacheDependency : CacheDependency | |
{ | |
/// <summary> | |
/// Initializes a new instance of the CloudQueueCacheDependency class | |
/// </summary> | |
/// <param name="key">The key that identifies the cached item</param> | |
public CloudQueueCacheDependency(string key) | |
{ | |
this.Id = key; | |
CloudQueueCacheDependencyMonitor.Instance.RegisterDependency(this); | |
} | |
/// <summary> | |
/// Gets or sets the unique identifier of the cache dependency | |
/// </summary> | |
public string Id { get; set; } | |
/// <summary> | |
/// Notifies the ASP.NET caching infrastructure that the dependency associated has been changed | |
/// </summary> | |
public void NotifyQueueDependencyChanged() | |
{ | |
this.NotifyDependencyChanged(this, EventArgs.Empty); | |
} | |
/// <summary> | |
/// Called by ASP.NET cache when the dependency is being disposed | |
/// </summary> | |
protected override void DependencyDispose() | |
{ | |
CloudQueueCacheDependencyMonitor.Instance.ReleaseDependency(this); | |
base.DependencyDispose(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Southworks.WindowsAzure | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net; | |
using System.Threading; | |
using Microsoft.WindowsAzure; | |
using Microsoft.WindowsAzure.ServiceRuntime; | |
using Microsoft.WindowsAzure.StorageClient; | |
/// <summary> | |
/// This is a singleton class that will spin up a thread checks a Windows Azure queue named by default "invalidatecache-roleinstanceid". | |
/// </summary> | |
/// <remarks> | |
/// This is intendede to be started once (e.g.: in the Application_Start) in a Windows Azure web role. | |
/// A queue will be created and listened for each instance of the web role. | |
/// For instance, if your role name is MyWebApp, the instance id will be MyWebApp_IN_X where X is the instance number. | |
/// So if you are running two instances of a web role, calling CloudQueueCacheDependencyMonitor.Start will create and listen thse two queues "invalidatecache-mywebapp-in-0" and "invalidatecache-mywebapp-in-1". | |
/// </remarks> | |
public class CloudQueueCacheDependencyMonitor | |
{ | |
private static readonly CloudQueueCacheDependencyMonitor instance = new CloudQueueCacheDependencyMonitor(); | |
private const int DefaultSleepTimeInMilliseconds = 3000; | |
private const string DefaultStorageConnectionString = "DataConnectionString"; | |
private const string DefaultQueueName = "invalidatecache"; | |
private int intervalInMilliseconds; | |
private string prefixQueueName; | |
private CloudQueue queue; | |
private List<CloudQueueCacheDependency> dependencies = new List<CloudQueueCacheDependency>(); | |
private object syncLock = new object(); | |
/// <summary> | |
/// Gets the singleton instance that allows using the CloudQueueCacheDependencyMonitor | |
/// </summary> | |
public static CloudQueueCacheDependencyMonitor Instance | |
{ | |
get | |
{ | |
return instance; | |
} | |
} | |
/// <summary> | |
/// Starts a background thread that will monitor the queue | |
/// "invalidatecache-{roleinstanceid}" in a storage account specified | |
/// in config with the name "DataConnectionString" | |
/// </summary> | |
public void Start() | |
{ | |
this.Start(DefaultQueueName, DefaultStorageConnectionString, DefaultSleepTimeInMilliseconds); | |
} | |
/// <summary> | |
/// Starts a background thread that will monitor the queue in a specific storage account | |
/// </summary> | |
/// <param name="prefixQueueName">The queue to monitor (this will be the prefix that will be appended to the role instance id sanitized)</param> | |
/// <param name="connectionStringName">The connection string of the storage account where the queue will be created</param> | |
/// <param name="intervalInMilliseconds">The time between each queue peek</param> | |
public void Start(string prefixQueueName, string connectionStringName, int intervalInMilliseconds) | |
{ | |
this.intervalInMilliseconds = intervalInMilliseconds; | |
this.prefixQueueName = prefixQueueName; | |
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) => configSetter(RoleEnvironment.GetConfigurationSettingValue(configName))); | |
this.CreateQueue(prefixQueueName, connectionStringName); | |
ThreadPool.QueueUserWorkItem((state) => { Work(); }); | |
} | |
/// <summary> | |
/// Register the dependency so that it gets notified when a message is dequeued and matches the Id | |
/// </summary> | |
/// <param name="dependency">A dependency that needs to be monitored</param> | |
public void RegisterDependency(CloudQueueCacheDependency dependency) | |
{ | |
if (!this.dependencies.Exists(dep => dep.Id.Equals(dependency.Id, StringComparison.OrdinalIgnoreCase))) | |
{ | |
lock (this.syncLock) | |
{ | |
this.dependencies.Add(dependency); | |
} | |
} | |
} | |
/// <summary> | |
/// Deletes the reference to the dependency | |
/// </summary> | |
/// <param name="dependency">A dependency previously registered</param> | |
public void ReleaseDependency(CloudQueueCacheDependency dependency) | |
{ | |
if (this.dependencies.Exists(dep => dep.Id.Equals(dependency.Id, StringComparison.OrdinalIgnoreCase))) | |
{ | |
lock (this.syncLock) | |
{ | |
this.dependencies.Remove(dependency); | |
} | |
} | |
} | |
private void CreateQueue(string queueName, string connectionStringName) | |
{ | |
var account = CloudStorageAccount.FromConfigurationSetting(connectionStringName); | |
// turning nagle off to increase performance | |
ServicePoint queueServicePoint = ServicePointManager.FindServicePoint(account.QueueEndpoint); | |
queueServicePoint.UseNagleAlgorithm = false; | |
var client = account.CreateCloudQueueClient(); | |
string instanceQueueName = string.Format("{0}-{1}", queueName, this.GetRoleInstanceId()); | |
this.queue = client.GetQueueReference(instanceQueueName.ToLowerInvariant()); | |
this.queue.CreateIfNotExist(); | |
} | |
private string GetRoleInstanceId() | |
{ | |
string roleInstance = RoleEnvironment.CurrentRoleInstance.Id; | |
string sanitized; | |
if (roleInstance.StartsWith("deployment")) | |
{ | |
sanitized = roleInstance.Substring(roleInstance.IndexOf('.') + 1).Replace(".", "-"); | |
} | |
else | |
{ | |
sanitized = roleInstance.Replace(".", "-").Replace("_", "-"); | |
} | |
if (sanitized.Length > 64) | |
{ | |
sanitized = sanitized.Remove(0, sanitized.IndexOf("-") + 1); | |
if (sanitized.Length > 64) | |
{ | |
sanitized = sanitized.Remove(0, sanitized.IndexOf("-") + 1); | |
} | |
} | |
return sanitized; | |
} | |
private void Work() | |
{ | |
while (true) | |
{ | |
var messages = this.queue.GetMessages(32); | |
if (messages != null) | |
{ | |
foreach (var message in messages) | |
{ | |
this.ProcessMessage(message); | |
this.queue.DeleteMessage(message); | |
} | |
} | |
Thread.Sleep(this.intervalInMilliseconds); | |
} | |
} | |
private void ProcessMessage(CloudQueueMessage message) | |
{ | |
var content = message.AsString; | |
var dependency = this.dependencies.SingleOrDefault(dep => dep.Id.Equals(content, StringComparison.OrdinalIgnoreCase)); | |
if (dependency != null) | |
{ | |
dependency.NotifyQueueDependencyChanged(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment