Cloud applications are distributed applications by design. There are many instances of roles located on different VMs and everything is load balanced. In Windows Azure there is no sticky load balancing so you can never know which role instance will serve your request. Such an environment introduces the challenge of state management. This is not a new challenge we know it from Asp.Net applications. Now in windows azure we find it everywhere – In web roles and worker roles.
When you create a object and save data in its member you want to keep working with the same object instance because your data is there. In a distributed applications it is not possible because objects instances cannot be shared across VMs.
So all state (information traditionally placed in object members) must be moved to a shared location. Somewhere all instances will have access to the data.
Azure storage is independent of Azure roles so it can function as a common place holder for state. Some data fit great in blobs or tables but usually it is not convenient to save simple data in Azure storage. saving objects in blobs requires serialization, persisting data in tables requires adding partition key and row key to every object.
Well … There one more option. Azure AppFabric Cache.
Azure AppFabric Cache is a distributed cache independent of Azure roles where you can persist objects (the only requirement on the objects is that they should be DataContract serializable)
It is simple. It is scalable and safe.
I wrote a little State Manager that I use in my Azure applications. Instead of saving state in my business objects I export it to this state manager.
/// <summary>
/// Provides a State management infrastructure for distributed stateless services which needs to save state (i.e. data)
/// The State management infrastructure is independent and distributed which allows the services to remain stateless.
/// This State management infrastructure is implemented using Azure AppFabric Distributed Cache.
/// </summary>
public static class StateManager
{
private static DataCacheFactory CacheFactory;
private static Dictionary<string,DataCacheLockHandle> lockHandle;
static StateManager()
{
CacheFactory = new DataCacheFactory();
lockHandle = new Dictionary<string, DataCacheLockHandle>();
}
/// <summary>
/// Get an object which was previously saved in the state manager
/// </summary>
/// <param name="stateID"></param>
/// <param name="key"></param>
/// <returns></returns>
public static object GetState(string stateID, string key)
{
Contract.Requires(!string.IsNullOrEmpty(stateID));
Contract.Requires(!string.IsNullOrEmpty(key));
try
{
var cache = CacheFactory.GetDefaultCache();
if (cache == null)
throw new DataCacheException("Could not create a cache object");
return cache.Get(stateID + key);
}
catch (DataCacheException ex)
{
if (ex.ErrorCode != 6) //Key referred to does not exist
Logger.HandleException(ex);
return null;
}
catch (Exception ex)
{
Logger.HandleException(ex);
return null;
}
}
/// <summary>
/// Get an object which was previously saved in the state manager yet lock it for lockDuration
/// </summary>
/// <param name="stateID"></param>
/// <param name="key"></param>
/// <param name="lockDuration"></param>
/// <returns></returns>
public static object GetStateAndLock(string stateID, string key, TimeSpan lockDuration)
{
Contract.Requires(!string.IsNullOrEmpty(stateID));
Contract.Requires(!string.IsNullOrEmpty(key));
try
{
var cache = CacheFactory.GetDefaultCache();
if (cache == null)
throw new DataCacheException("Could not create a cache object");
DataCacheLockHandle handle;
var result = cache.GetAndLock(stateID + key,
lockDuration, out handle);
lockHandle[stateID + key] = handle;
return result;
}
catch (DataCacheException ex)
{
if (ex.ErrorCode != 6) //Key referred to does not exist
Logger.HandleException(ex);
return null;
}
catch (Exception ex)
{
Logger.HandleException(ex);
return null;
}
}
/// <summary>
/// Save an object in the state manager
/// </summary>
/// <param name="stateID"></param>
/// <param name="key"></param>
/// <param name="state"></param>
public static void PutState(string stateID, string key, object state)
{
Contract.Requires(!string.IsNullOrEmpty(stateID));
Contract.Requires(!string.IsNullOrEmpty(key));
try
{
var cache = CacheFactory.GetDefaultCache();
if (cache == null)
throw new DataCacheException("Could not create a cache object");
cache.Put(stateID+key, state);
}
catch (Exception ex)
{
Logger.HandleException(ex);
return null;
}
}
public static void PutState(string stateID, string key, object state, TimeSpan invalidationTimeout)
{
Contract.Requires(!string.IsNullOrEmpty(stateID));
Contract.Requires(!string.IsNullOrEmpty(key));
try
{
var cache = CacheFactory.GetDefaultCache();
if (cache == null)
throw new DataCacheException("Could not create a cache object");
cache.Put(stateID + key, state, invalidationTimeout);
}
catch (Exception ex)
{
Logger.HandleException(ex);
return null;
}
}
/// <summary>
/// Save an object in the state manager and unlock it
/// </summary>
/// <param name="stateID"></param>
/// <param name="key"></param>
/// <param name="state"></param>
public static void PutStateAndUnlock(string stateID, string key, object state)
{
Contract.Requires(!string.IsNullOrEmpty(stateID));
Contract.Requires(!string.IsNullOrEmpty(key));
try
{
var cache = CacheFactory.GetDefaultCache();
if (cache == null)
throw new DataCacheException("Could not create a cache object");
if (lockHandle.ContainsKey(stateID + key))
{
cache.PutAndUnlock(stateID + key, state, lockHandle[stateID + key]);
lockHandle.Remove(stateID + key)
}
else
cache.Put(stateID + key, state);
}
catch (Exception ex)
{
Logger.HandleException(ex);
return null;
}
}
/// <summary>
/// Clear an object from the state manager
/// </summary>
/// <param name="stateID"></param>
/// <param name="key"></param>
public static void RemoveState(string stateID, string key)
{
Contract.Requires(!string.IsNullOrEmpty(stateID));
Contract.Requires(!string.IsNullOrEmpty(key));
try
{
var cache = CacheFactory.GetDefaultCache();
if (cache == null)
throw new DataCacheException("Could not create a cache object");
cache.Remove(stateID + key);
if (lockHandle.ContainsKey(stateID + key))
lockHandle.Remove(stateID + key);
}
catch (Exception ex)
{
Logger.HandleException(ex);
return null;
}
}
}