Kevin Marshall's Epic Work Blog for Awesome People

@ksmarshall - i <3 software
in

Experimenting with inter-role communication on Azure for caching

It’s been awhile since I’ve used Azure. The new storage libraries are a welcome addition. As a better diagnostic framework and inter-role communication. In a previous Azure project, I wanted to use ASP.NET caching on the web role, but that breaks down if you start to run multiple instances. With inter-role communication it seems like you could use caching on the server as long as you notify the other instances of updates to the cache. I wrote a little prototype to do that. It’s fairly basic and I haven’t tested it on a real deployment or benchmarked etc. Basically this may just be a terrible way of doing things.

First I defined a contract for the cache messages:

   1:  using System.Collections.Generic;
   2:  using System.ServiceModel;
   3:   
   4:  namespace MvcWebRole1.Caching
   5:  {
   6:      /// <summary>
   7:      /// Defines the contract for the cache service.
   8:      /// </summary>
   9:      [ServiceContract]
  10:      public interface ICacheService
  11:      {
  12:          [OperationContract(IsOneWay = true)]
  13:          void Insert(string key, object value);
  14:   
  15:          [OperationContract(IsOneWay = true)]
  16:          void Remove(string key);
  17:      }
  18:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Next I implemented the service for that contract.

   1:  using System;
   2:  using System.Web;
   3:  using System.Diagnostics;
   4:  using System.Linq;
   5:  using System.ServiceModel;
   6:  using System.Threading;
   7:  using Microsoft.WindowsAzure.Diagnostics;
   8:  using Microsoft.WindowsAzure.ServiceRuntime;
   9:   
  10:  namespace MvcWebRole1.Caching
  11:  {
  12:  /// <summary>
  13:      /// Implementation of the WCF cache service.
  14:      /// </summary>
  15:      [ServiceBehavior(
  16:          InstanceContextMode = InstanceContextMode.Single,
  17:          ConcurrencyMode = ConcurrencyMode.Multiple,
  18:  #if DEBUG
  19:          IncludeExceptionDetailInFaults = true,
  20:  #else
  21:          IncludeExceptionDetailInFaults = false,
  22:  #endif
  23:   AddressFilterMode = AddressFilterMode.Any)]
  24:      public class CacheService : ICacheService
  25:      {
  26:          public void Insert(string key, object value)
  27:          {
  28:              HttpRuntime.Cache.Insert(key, value);
  29:              Trace.TraceInformation(String.Format("Added: {0} to key: {1} from broadcast", value, key));
  30:          }
  31:   
  32:          public void Remove(string key)
  33:          {
  34:              HttpRuntime.Cache.Remove(key);
  35:              Trace.TraceInformation(String.Format("Removed key: {0} from broadcast", key));
  36:          }
  37:      }
  38:  }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

That handles the inserts / deletes the cached items when one instance broadcasts a cache update.

The next bit of code is used to cache on one instance and then broadcast that info out to the other roles.

   1:  using System;
   2:  using System.Diagnostics;
   3:  using System.Linq;
   4:  using System.ServiceModel;
   5:  using System.Threading;
   6:  using System.Web;
   7:  using Microsoft.WindowsAzure.Diagnostics;
   8:  using Microsoft.WindowsAzure.ServiceRuntime;
   9:   
  10:  namespace MvcWebRole1.Caching
  11:  {
  12:      public class SynchedCache
  13:      {
  14:          private enum BroadcastType
  15:          {
  16:              Insert,
  17:              Remove
  18:          }
  19:   
  20:          public static void Insert(string key, object value)
  21:          {
  22:              HttpRuntime.Cache.Insert(key, value);
  23:              Trace.TraceInformation(String.Format("Added: {0} to key: {1}", value, key));
  24:              BroadcastCacheUpdate(BroadcastType.Insert, new object[] {key, value});
  25:          }
  26:   
  27:          public static void Remove(string key)
  28:          {
  29:              HttpRuntime.Cache.Remove(key);
  30:              Trace.TraceInformation(String.Format("Removed key: {0}", key));
  31:              BroadcastCacheUpdate(BroadcastType.Remove, new object[] { key });
  32:          }
  33:   
  34:          public static object Get(string key)
  35:          {
  36:              return HttpRuntime.Cache.Get(key);
  37:          }
  38:   
  39:          private static void BroadcastCacheUpdate(BroadcastType type, params object[] args)
  40:          {
  41:              // iterate over all instances of the internal endpoint except the current role - no need to notify itself
  42:              var current = RoleEnvironment.CurrentRoleInstance;
  43:              var endPoints = current.Role.Instances.Where(instance => instance != current)
  44:                              .Select(instance => instance.InstanceEndpoints["InternalHttpIn"]);
  45:   
  46:              foreach (var ep in endPoints)
  47:              {
  48:                  EndpointAddress address =
  49:                      new EndpointAddress(String.Format("http://{0}/InternalHttpIn", ep.IPEndpoint));
  50:                  ICacheService client = WebRole.Factory.CreateChannel(address);
  51:   
  52:                  try
  53:                  {
  54:                      string key = args[0].ToString();
  55:   
  56:                      switch (type)
  57:                      {
  58:                          case BroadcastType.Insert:
  59:                              client.Insert(key, args[1]);
  60:                              break;
  61:   
  62:                          case BroadcastType.Remove:
  63:                              client.Remove(key);
  64:                              break;
  65:                      }
  66:   
  67:                      ((ICommunicationObject)client).Close();
  68:                  }
  69:                  catch (TimeoutException timeoutException)
  70:                  {
  71:                      Trace.TraceError("Unable to notify web role instance '{0}'. The service operation timed out. {1}", 
  72:                          ep.RoleInstance.Id, timeoutException.Message);
  73:                      ((ICommunicationObject)client).Abort();
  74:                  }
  75:                  catch (CommunicationException communicationException)
  76:                  {
  77:                      Trace.TraceError("Unable to notify web role instance '{0}'. There was a communication problem. {1} - {2}", 
  78:                          ep.RoleInstance.Id, communicationException.Message, communicationException.StackTrace);
  79:                      ((ICommunicationObject)client).Abort();
  80:                  }
  81:              }
  82:          }
  83:      }
  84:  }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

Finally in your page you can perform operations on the cache like so.

SynchedCache.Insert(key, DateTime.Now.ToString());.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }

That call will then notify all the other web roles running. Of course running AppFabric Caching on Azure would be a much better solution once available, but the inter-role communication is still nice to have for other stuff.

Comments

No Comments