In my last post I showed you how to run memcached, the popular distributed hash table, on Windows. Now it's time to start using it from your .NET application.
Getting the client
The Win32 client can be downloaded here. It's a source release, available for .NET 1.1 and 2.0, so you'll need to compile the solution before you can use it. Once you have compiled it, create a new console application and add a reference toMemcached.ClientLibrary
.
Writing your first memcached program
As it's just really a very scalable hash table, memcached is very simple to code against. There are only a few things you need to configure.memcached requires a list of all the servers in the pool to connect to. The hash function that memcached uses to choose which machine to store values on requires knowledge of all the machines in the pool to function correctly, so maintaining this list is a concern when using memcached in the pool. For now, however, we'll just use a single local server for simplicity, running with default port (11211) and cache size (64Mb), running with the very verbose option - this will allow us to monitor the memcached process from the command line.
C:\memcached>memcached -vv
Now to write some code. We are running a single server locally, so let's let memcached know about it.
string[] servers = { "127.0.0.1:11211" }; SockIOPool pool = SockIOPool.GetInstance(); pool.SetServers(servers); pool.Initialize();
Our memcached client is now ready to use. Let's try and stick some values in the cache and get them back out again.
// Create the client MemcachedClient mc = new MemcachedClient(); // Set the value in the cache mc.Set("andy", "rocks"); // Make sure it's there Console.WriteLine("The key " + (mc.KeyExists("andy") ? "exists" : "doesn't exist") + "!"); // Fetch from the cache string cachedValue = mc.Get("andy") as string; // Display the fetched value Console.WriteLine("Retrieved the value '" + cachedValue + "' from the cache!");
If we go back to our
memcached.exe
window, something like the following should appear when the above is run:
<96 new client connection <100 new client connection <104 new client connection <104 set andy 0 0 6 >104 STORED <104 get andy >104 sending key andy >104 END <104 get andy >104 sending key andy >104 END <104 connection closed. <96 connection closed. <100 connection closed.
The
MemcachedClient.Get()
method returns a value of type object
. This you can cast to whatever type you originally put in the cache.
However, if you want to store objects, then you must serialize them, either yourself, or with the built-in .NET serialization mechanisms.
If the memcached server is not found or an error occurs during
sets
and gets
, no exception will be thrown to the calling code. Basically, if your cache servers encounter a problem, to your code it will just appear as if the cache is permanently empty. Bad for performance, but good for uptime.
Using with Linq Queries
C# 3 and Linq's syntax extensions allow us to write some funky shortcuts for things like caching systems. We can cache the results of DLinq queries in memcached the same as any other data, as long as we make sure we have a serialization policy for the classes.For example, the following code from a data access layer method to return all the users of a site will first check memcached for the data. If it is not found, it will execute the query and store the results in memcached. The code to manage the caching is written in the form of an extension method, so you can simply add a single call to your DAL methods to enable memcached caching of query results.
Extension method:
public static IEnumerable<T> CachedQuery<T> (this IQueryable<T> query, string key) where T : class { if (cache.KeyExists(key)) { return (IEnumerable<T>)cache.Get(key); } else { IEnumerable<T> items = query.ToList(); cache.Set(key, items); return items; } }
Example Usage:
public static IEnumerable<User> GetAllUsers() { // Retrieve from cache if it exists, otherwise run the query return (from u in ctx.Users select u).CachedQuery("allusers"); }
The above code ignores nasty things like serialization, but they are easily added in. There is an effort to standardize an object serialization format for use in memcached (very useful when you have clients from multiple platforms and languages accessing the same pools) to JSON, but you are free to choose the serialization mechanism. I would recommend against XML however, due to the excessive redundancy and large documents produced in the language.
Multiple Servers
Using memcached with multiple servers in the pool is almost as easy as using with a single one. Just supply theSockIOPool
class with the list of all the servers you wish to pool together.
string[] servers = { "192.168.1.100:11211", "192.168.1.101:11211", "192.168.1.102:11211", "192.168.1.103:11211", "192.168.1.104:11211", }; SockIOPool pool = SockIOPool.GetInstance(); pool.SetServers(servers); pool.Initialize();
Note, that all your clients must be aware of the same set of servers, otherwise the hashing function to find data on servers will not work consistently between clients, which will adversely affect your hit ratio.