Archive for the ‘Distributed Cache’ Tag
Wishlist for Velocity (CPT4?)
I went to Rob’s presentation on Velocity tonight, it’s a great presentation and a lot of information shared with audience.
I prepare a wishlist for Velocity (CPT4 or next beta release) and hope the Velocity team can take my wishlist into consideration.
- A new method: bool DataCache.ContainsRegion(string region).
- This method checks if the region exists in the named cache or not.
- See my previous post for details.
- Update the method DataCache.GetObjectsByAnyTag(List<DataCacheTag> tags, string region) so the keys of returned cache items are unique.
- Make sure the cache key is not contained in the keys, then add the item into the collection.
- See my previous post for example.
- Make sure the default region name is not arbitratry.
- DataCache.GetCacheItem Method – Gets a DataCacheItem object to retrieve all information associated with your cached object in the cluster.
- DataCache.GetCacheItem (String) -> Gets a DataCacheItem object to retrieve all information associated with your cached object in the cluster.
- DataCache.GetCacheItem (String, String) -> Gets a DataCacheItem object to retrieve all information associated with your cached object in the cluster. For objects stored in regions.
- When using the DataCache.GetCacheItem (String) method (without region specified), the return object’s property -RegionName – is kind of arbitrary.
- A new commad for administrators to create region in PowerShell.
- New-CacheRegion [-CacheName] <CacheName> [-RegionName] <RegionName>
- Example 1:
- New-CacheRegion -CacheName Products -RegionName MyRegion -> this creates a region “MyRegion” in the named cache “Products”.
- Example 2:
- New-CacheRegion -RegionName MyRegion -> this creates a region “MyRegion” in the default cache “default”
Velocity (CTP3) – DataCache.GetObjectsByAnyTag Method
I was writing some code to experiment the Tag search in Velocity CTP3 and I found there is some small bug in one of the get objects by tag methods – DataCache.GetObjectsByAnyTag.
The definition of the method of DataCache.GetObjectsByAnyTag is:
Gets an enumerable list of all cached objects in the specified region that have any of the same tags in common.
For example I have 3 cache items:
Item 1: key = “111″, content = “aaa”, tag = “a”
Item 2: key = “222″, content = “bbb”, tag = “b”
Item 3: key = “333″, content = “ccc”, tag = “a,b,c”
Here are some scenarios:
(1) If I search with Tag “a” via DataCache.GetObjectsByAnyTag(<a>, “my_region”), I get:
Item 1: key = “111″, content = “aaa”, tag = “a”
Item 3: key = “333″, content = “ccc”, tag = “a,b,c”
(2) If I search with Tag “b” via DataCache.GetObjectsByAnyTag(<b>, “my_region”), I get:
Item 2: key = “222″, content = “bbb”, tag = “b”
Item 3: key = “333″, content = “ccc”, tag = “a,b,c”
(3) But if I search with Tags “a,b” via DataCache.GetObjectsByAnyTag(<a, b>, “my_region”), I get:
Item 1: key = “111″, content = “aaa”, tag = “a”
Item 2: key = “222″, content = “bbb”, tag = “b”
Item 3: key = “333″, content = “ccc”, tag = “a,b,c”
Item 3: key = “333″, content = “ccc”, tag = “a,b,c”
In scenario (3), the item 3 has been added twice because its tags are “a,b,c” and I search by <a,b> (Any Tag).
I believe this method - DataCache.GetObjectsByAnyTag - should check if the cache key is contained in the collection, if not, then add it to the collection – unless the velocity team want the developers do the checking of duplicated key in the caller program.
I will list my code and some screenshots.
The first file is CacheWorker.cs:
using System;
using Microsoft.Data.Caching;
namespace VelocityTest
{
public class CacheWorker
{
public const string DEFAULT_CACHE_REGION = “Default_Cache_Region”;
private static object _synclock = new object();
private static CacheWorker _cacheWorker = null;
private static DataCacheFactory _cacheFactory = null;
private static DataCache _defaultCache = null;
private static DataCache _eventCache = null;
private CacheWorker()
{
}
public static CacheWorker Instance
{
get
{
lock (_synclock)
{
try
{
if (_cacheWorker == null)
{
_cacheWorker = new CacheWorker();
// we use configuration file
_cacheFactory = new DataCacheFactory();
// default cache
_defaultCache = _cacheFactory.GetDefaultCache();
_defaultCache.RemoveRegion(DEFAULT_CACHE_REGION);
_defaultCache.CreateRegion(DEFAULT_CACHE_REGION, true);
// names cache
_eventCache = _cacheFactory.GetCache(“Event2″);
}
return _cacheWorker;
}
catch (DataCacheException ex)
{
return null;
}
}
}
}
public DataCacheFactory CacheFactory
{
get { return _cacheFactory; }
}
public DataCache DefaultCache
{
get { return _defaultCache; }
}
public DataCache EventCache
{
get { return _eventCache; }
}
public DataCacheItemVersion AddCache(string key, object value, TimeSpan? timeout)
{
DataCacheItemVersion version = null;
if (timeout.HasValue)
{
version = _defaultCache.Put(key, value, timeout.Value);
}
else
{
version = _defaultCache.Put(key, value);
}
return version;
}
}
}
The second file is Default3.aspx page:
<%@ Page Language=”C#” AutoEventWireup=”true” CodeBehind=”Default3.aspx.cs” Inherits=”VelocityTest.Default3″ %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml” >
<head runat=”server”>
<title>Velocity – Tag Example</title>
</head>
<body>
<form id=”form1″ runat=”server”>
<p>
<h3>Velocity – Tag Example</h3>
<table border=”0″ cellpadding=”2″>
<tr>
<td><b>Cache Key</b></td>
<td>
<asp:TextBox ID=”txtCacheKey” runat=”server” />
<asp:RequiredFieldValidator ID=”vadCacheKey” runat=”server” ControlToValidate=”txtCacheKey” ErrorMessage=”* required” Display=”Dynamic” />
</td>
</tr>
<tr>
<td><b>Cache Content</b></td>
<td>
<asp:TextBox ID=”txtCacheContent” runat=”server” />
<asp:RequiredFieldValidator ID=”vadCacheContent” runat=”server” ControlToValidate=”txtCacheContent” ErrorMessage=”* required” Display=”Dynamic” />
</td>
</tr>
<tr>
<td><b>Cache Tags</b></td>
<td><asp:TextBox ID=”txtCacheTag” runat=”server” />(Tags are seperated by comma)</td>
</tr>
<tr>
<td> </td>
<td>
<asp:Button ID=”btnCancel” runat=”server” Text=”Cancel” CausesValidation=”false” />
<asp:Button ID=”btnSave” runat=”server” Text=”Save” />
</td>
</tr>
</table>
</p>
<hr />
<p>
<table border=”0″ cellpadding=”2″>
<tr>
<td><b>Search Method:</b></td>
<td>
<asp:RadioButtonList ID=”radSearchMethodList” runat=”server” RepeatDirection=”Horizontal”>
<asp:ListItem Text=”No Tag” Value=”NoTag” />
<asp:ListItem Text=”All Tags” Value=”AllTags” />
<asp:ListItem Text=”Any Tag” Value=”AnyTag” />
<asp:ListItem Text=”One Tag” Value=”OneTag” />
</asp:RadioButtonList>
</td>
</tr>
<tr>
<td><b>Search Tags:</b></td>
<td><asp:TextBox ID=”txtSearchTag” runat=”server” />(Tags are seperated by comma)</td>
</tr>
<tr>
<td> </td>
<td>
<asp:Button ID=”btnClearSearch” runat=”server” Text=”Clear” CausesValidation=”false” />
<asp:Button ID=”btnSearch” runat=”server” Text=”Search” CausesValidation=”false” />
</td>
</tr>
</table>
</p>
<hr />
<p>
<asp:GridView ID=”gvCache” runat=”server”
AutoGenerateColumns=”false”
EmptyDataText=”No cache data”>
<Columns>
<asp:TemplateField HeaderText=”Key” ShowHeader=”true”>
<ItemTemplate>
<asp:Label ID=”lblCacheKey” runat=”server” />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText=”Content” ShowHeader=”true”>
<ItemTemplate>
<asp:Label ID=”lblCacheContent” runat=”server” />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText=”Tags” ShowHeader=”true”>
<ItemTemplate>
<asp:Label ID=”lblCacheTag” runat=”server” />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</p>
</form>
</body>
</html>
The third file is Default3.aspx.cs code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
using Microsoft.Data.Caching;
namespace VelocityTest
{
[Serializable]
public class MyCacheItem
{
public string Key { get; set; }
public string Content { get; set; }
public List<DataCacheTag> Tags { get; set; }
public MyCacheItem(string key, string conent)
{
Key = key;
Content = conent;
Tags = new List<DataCacheTag>();
}
public MyCacheItem(string key, string conent, string[] tags)
{
Key = key;
Content = conent;
Tags = new List<DataCacheTag>();
foreach (string tag in tags)
{
Tags.Add(new DataCacheTag(tag));
}
}
public string[] GetTags()
{
string[] tags = null;
if (Tags != null && Tags.Count > 0)
{
tags = new string[Tags.Count];
int i = 0;
foreach (DataCacheTag tag in Tags)
{
tags[i++] = tag.ToString();
}
}
return tags;
}
}
public enum TagSearchMethod
{
NoTag,
AllTags,
AnyTag,
OneTag
}
public partial class Default3 : System.Web.UI.Page
{
protected const string DEFAULT_DELIMITER = “,”;
protected DataCacheFactory _factory = CacheWorker.Instance.CacheFactory;
protected DataCache _cache = CacheWorker.Instance.DefaultCache;
protected void Page_Load(object sender, EventArgs e)
{
btnSave.Click += new EventHandler(btnSave_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
btnClearSearch.Click += new EventHandler(btnClearSearch_Click);
btnSearch.Click += new EventHandler(btnSearch_Click);
gvCache.RowDataBound += new GridViewRowEventHandler(gvCache_RowDataBound);
gvCache.DataSource = GetCacheDataSource(TagSearchMethod.NoTag, null);
gvCache.DataBind();
}
protected List<MyCacheItem> GetCacheDataSource(TagSearchMethod method, List<DataCacheTag> tags)
{
List<MyCacheItem> items = new List<MyCacheItem>();
IEnumerable<KeyValuePair<string, Object>> cachePairs = null;
if (method == TagSearchMethod.NoTag)
{
cachePairs = _cache.GetObjectsInRegion(CacheWorker.DEFAULT_CACHE_REGION);
}
else if (method == TagSearchMethod.AllTags)
{
cachePairs = _cache.GetObjectsByAllTags(tags, CacheWorker.DEFAULT_CACHE_REGION);
}
else if (method == TagSearchMethod.AnyTag)
{
cachePairs = _cache.GetObjectsByAnyTag(tags, CacheWorker.DEFAULT_CACHE_REGION);
}
else if (method == TagSearchMethod.OneTag)
{
cachePairs = _cache.GetObjectsByTag(tags[0], CacheWorker.DEFAULT_CACHE_REGION);
}
if (cachePairs != null)
{
foreach (KeyValuePair<string, object> item in cachePairs)
{
items.Add((MyCacheItem)item.Value);
}
}
return items;
}
protected void gvCache_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
MyCacheItem data = e.Row.DataItem as MyCacheItem;
if (data != null)
{
Label lblCacheKey = e.Row.FindControl(“lblCacheKey”) as Label;
if (lblCacheKey != null)
{
lblCacheKey.Text = data.Key;
}
Label lblCacheContent = e.Row.FindControl(“lblCacheContent”) as Label;
if (lblCacheContent != null)
{
lblCacheContent.Text = data.Content;
}
Label lblCacheTag = e.Row.FindControl(“lblCacheTag”) as Label;
if (lblCacheTag != null)
{
if (data.Tags != null)
{
lblCacheTag.Text = String.Join(“,”, data.GetTags());
}
else
{
lblCacheTag.Text = “ ”;
}
}
}
}
}
protected void btnSearch_Click(object sender, EventArgs e)
{
if (radSearchMethodList.SelectedItem != null)
{
TagSearchMethod method = (TagSearchMethod)Enum.Parse(typeof(TagSearchMethod), radSearchMethodList.SelectedValue);
List<DataCacheTag> tags = GetTags(txtSearchTag.Text.Trim());
gvCache.DataSource = GetCacheDataSource(method, tags);
}
else
{
gvCache.DataSource = GetCacheDataSource(TagSearchMethod.NoTag, null);
}
gvCache.DataBind();
}
protected void btnClearSearch_Click(object sender, EventArgs e)
{
Response.Redirect(Request.RawUrl);
}
protected void btnCancel_Click(object sender, EventArgs e)
{
Response.Redirect(Request.RawUrl);
}
protected void btnSave_Click(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(txtCacheKey.Text.Trim()) &&
!String.IsNullOrEmpty(txtCacheContent.Text.Trim()))
{
MyCacheItem item = null;
string[] tags = null;
if (!String.IsNullOrEmpty(txtCacheTag.Text.Trim()))
{
tags = (string[])txtCacheTag.Text.Trim().Split(DEFAULT_DELIMITER.ToCharArray());
}
if (tags != null && tags.Length > 0)
{
item = new MyCacheItem(txtCacheKey.Text.Trim(), txtCacheContent.Text.Trim(), tags);
}
else
{
item = new MyCacheItem(txtCacheKey.Text.Trim(), txtCacheContent.Text.Trim());
}
_cache.Put(item.Key, item, item.Tags, CacheWorker.DEFAULT_CACHE_REGION);
}
gvCache.DataSource = GetCacheDataSource(TagSearchMethod.NoTag, null);
gvCache.DataBind();
}
protected List<DataCacheTag> GetTags(string tagString)
{
List<DataCacheTag> tags = new List<DataCacheTag>();
string[] items = tagString.Split(DEFAULT_DELIMITER.ToCharArray());
foreach (string item in items)
{
tags.Add(new DataCacheTag(item));
}
return tags;
}
}
}
The following are screenshots.
This screenshot has nothing is cache.

This screenshot has 3 cache items.

This screenshot is search by AllTags <a>.

This screenshot is search by AllTags <a, b>.

This screenshot is search by AnyTag <a>.

This screenshot is search by AnyTag <b>.

This screenshot is search by Tag <a, b> – there are 4 cache items!

Use Velocity (CTP3) as Session State Provider
The blog shows how to configure web.config to use Velocity as session state provider, but it only works for CTP2.
I did some experiment and found out the following configuration in web.config will work in Velocity CTP3:
<sessionState mode=“Custom“ customProvider=“SessionStoreProvider“>
<providers>
<add name=“SessionStoreProvider“ type=“Microsoft.Data.Caching.DataCacheSessionStoreProvider, ClientLibrary“ />
</providers>
</sessionState>
Notification callback in Velocity CTP3
This is an example of how to make the Notification callback work in Velocity CTP3.
Here is my web.config – the cache is a routing client:
<?xml version=“1.0“?>
<configuration>
<configSections>
<!– required to read the <dataCacheClient> element –>
<section name=“dataCacheClient“
type=“Microsoft.Data.Caching.DataCacheClientSection,CacheBaseLibrary“
allowLocation=“true“
allowDefinition=“Everywhere“/>
<!– required to read the <fabric> element, when present –>
<section name=“fabric“
type=“System.Data.Fabric.Common.ConfigFile,FabricCommon“
allowLocation=“true“
allowDefinition=“Everywhere“/>
<!– routing client–>
<dataCacheClient deployment=“routing“>
<clientNotification pollInterval=“300“ />
<!– cache host(s) –>
<hosts>
<host
name=“VIRTUALB-UJT42H“
cachePort=“22233“
cacheHostName=“DistributedCacheService“/>
</hosts>
</dataCacheClient>
</configuration>
Here is the cache cluster configuration via the command Export-CacheClusterConfig:
<?xml version=“1.0“ encoding=“utf-8“?>
<configuration>
<configSections>
<section name=“dataCache“ type=“Microsoft.Data.Caching.DataCacheSection, CacheBaseLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91“ />
</configSections>
<dataCache cluster=“Velocity_Cluster_1“ size=“Small“>
<caches>
<cache type=“partitioned“ consistency=“strong“ name=“default“>
<policy>
<eviction type=“lru“ />
<expiration defaultTTL=“10“ isExpirable=“true“ />
<serverNotification isEnabled=“true“ />
</policy>
</cache>
<cache type=“partitioned“ consistency=“strong“ name=“default_2“>
<policy>
<eviction type=“lru“ />
<expiration defaultTTL=“10“ isExpirable=“true“ />
<serverNotification isEnabled=“true“ />
</policy>
</cache>
</caches>
<hosts>
<host clusterPort=“22234“ hostId=“1825498580“ size=“1024“ quorumHost=“true“
name=“VIRTUALB-UJT42H“ cacheHostName=“DistributedCacheService“
cachePort=“22233“ />
</hosts>
<advancedProperties>
<partitionStoreConnectionSettings providerName=“System.Data.SqlClient“
connectionString=“Data Source=VIRTUALB-UJT42H\SQLEXPRESS;Initial Catalog=Velocity_Shared_1;User Id=Not_sa;Password=ForYourEyesOnly;“
leadHostManagement=“false“ />
</advancedProperties>
</dataCache>
</configuration>
Here is the code of CacheWorker.cs (kind of helper class with singleton pattern):
using System;
using Microsoft.Data.Caching;
namespace VelocityTest
{
public class CacheWorker
{
private static object _synclock = new object();
private static CacheWorker _cacheWorker = null;
private static DataCacheFactory _cacheFactory = null;
private static DataCache _defaultCache = null;
private CacheWorker()
{
}
public static CacheWorker Instance
{
get
{
lock (_synclock)
{
try
{
if (_cacheWorker == null)
{
_cacheWorker = new CacheWorker();
// we use configuration file
_cacheFactory = new DataCacheFactory();
// default cache
_defaultCache = _cacheFactory.GetDefaultCache();
}
return _cacheWorker;
}
catch (DataCacheException ex)
{
return null;
}
}
}
}
public DataCacheFactory CacheFactory
{
get { return _cacheFactory; }
}
public DataCache DefaultCache
{
get { return _defaultCache; }
}
}
}
Here is the partial code-behind of the test page:
public partial class _Default : System.Web.UI.Page
{
public const string CACHE_KEY = “{8768036E-2B99-4563-8BA5-C2D07B5B6023}”;
protected static DataCacheFactory _factory = CacheWorker.Instance.CacheFactory;
protected static DataCache _cache = CacheWorker.Instance.DefaultCache;
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DataCacheOperation filter = DataCacheOperation.AddItem | DataCacheOperation.RemoveItem | DataCacheOperation.ReplaceItem | DataCacheOperation.ClearRegion | DataCacheOperation.CreateRegion | DataCacheOperation.RemoveRegion;
DataCacheNotificationDescriptor dn = _cache.AddCacheLevelCallback(filter, OnCacheNotificationReceived);
SetCache(MyPerson, true);
ShowCache(GetCache());
}
btnUpdate.Click += new EventHandler(btnUpdate_Click);
btnRefresh.Click += new EventHandler(btnRefresh_Click);
btnRemove.Click += new EventHandler(btnRemove_Click);
btnGet.Click += new EventHandler(btnGet_Click);
btnAnotherPage.Click += new EventHandler(btnAnotherPage_Click);
}
// other code is here….
public void OnCacheNotificationReceived(string cacheName, string regionName, string key, DataCacheItemVersion version, DataCacheOperation cacheOperation, DataCacheNotificationDescriptor nd)
{
StringBuilder sb = new StringBuilder();
sb.Append(“cacheName: “ + cacheName + Environment.NewLine);
sb.Append(“regionName: “ + regionName + Environment.NewLine);
sb.Append(“key: “ + key + Environment.NewLine);
sb.Append(“cacheOperation: “ + cacheOperation.ToString() + Environment.NewLine);
txtMessageBox.Text = sb.ToString();
}
}
Basically the configuration has the Notification enabled and set the client as routing client. The callback is fired when ever there is a change in cache item or region.
Leave a Comment
Leave a Comment
Leave a Comment