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.