The central object in StackExchange.Redis is the `ConnectionMultiplexer` class in the `StackExchange.Redis` namespace; this is the object that hides away the details of multiple servers. Because the `ConnectionMultiplexer` does a lot, it is designed to be **shared and reused** between callers. You should not create a `ConnectionMultiplexer` per operation. It is fully thread-safe and ready for this usage. In all the subsequent examples it will be assumed that you have a `ConnectionMultiplexer` instance stored away for re-use. But for now, let's create one. This is done using `ConnectionMultiplexer.Connect` or `ConnectionMultiplexer.ConnectAsync`, passing in either a configuration string or a `ConfigurationOptions` object. The configuration string can take the form of a comma-delimited series of nodes, so let's just connect to an instance on the local machine on the default port (6379):
Note that `ConnectionMultiplexer` implements `IDisposable` and can be disposed when no longer required, but I am deliberately not showing `using` statement usage, because it is exceptionally rare that you would want to use a `ConnectionMultiplexer` briefly, as the idea is to re-use this object.
A more complicated scenario might involve a master/slave setup; for this usage, simply specify all the desired nodes that make up that logical redis tier (it will automatically identify the master):
If it finds both nodes are masters, a tie-breaker key can optionally be specified that can be used to resolve the issue, however such a condition is fortunately very rare.
...
...
@@ -27,30 +31,38 @@ Using a redis database
Accessing a redis database is as simple as:
IDatabase db = redis.GetDatabase();
```C#
IDatabase db = redis.GetDatabase();
```
The object returned from `GetDatabase` is a cheap pass-thru object, and does not need to be stored. Note that redis supports multiple databases (although this is not supported on "cluster"); this can be optionally specified in the call to `GetDatabase`. Additionally, if you plan to make use of the asynchronous API and you require the [`Task.AsyncState`][2] to have a value, this can also be specified:
int databaseNumber = ...
object asyncState = ...
IDatabase db = redis.GetDatabase(databaseNumber, asyncState);
```C#
int databaseNumber = ...
object asyncState = ...
IDatabase db = redis.GetDatabase(databaseNumber, asyncState);
```
Once you have the `IDatabase`, it is simply a case of using the [redis API](http://redis.io/commands). Note that all methods have both synchronous and asynchronous implementations. In line with Microsoft's naming guidance, the asynchronous methods all end `...Async(...)`, and are fully `await`-able etc.
The simplest operation would be to store and retrieve a value:
string value = "abcdefg";
db.StringSet("mykey", value);
...
string value = db.StringGet("mykey");
Console.WriteLine(value); // writes: "abcdefg"
```C#
string value = "abcdefg";
db.StringSet("mykey", value);
...
string value = db.StringGet("mykey");
Console.WriteLine(value); // writes: "abcdefg"
```
Note that the `String...` prefix here denotes the [String redis type](http://redis.io/topics/data-types), and is largely separate to the [.NET String type][3], although both can store text data. However, redis allows raw binary data for both keys and values - the usage is identical:
byte[] key = ..., value = ...;
db.StringSet(key, value);
...
byte[] value = db.StringGet(key);
```C#
byte[] key = ..., value = ...;
db.StringSet(key, value);
...
byte[] value = db.StringGet(key);
```
The entire range of [redis database commands](http://redis.io/commands) covering all redis data types is available for use.
...
...
@@ -59,17 +71,23 @@ Using redis pub/sub
Another common use of redis is as a [pub/sub message](http://redis.io/topics/pubsub) distribution tool; this too is simple, and in the event of connection failure, the `ConnectionMultiplexer` will handle all the details of re-subscribing to the requested channels.
ISubscriber sub = redis.GetSubscriber();
```C#
ISubscriber sub = redis.GetSubscriber();
```
Again, the object returned from `GetSubscriber` is a cheap pass-thru object that does not need to be stored. The pub/sub API has no concept of databases, but as before we can optionally provide an async-state. Note that all subscriptions are global: they are not scoped to the lifetime of the `ISubscriber` instance. The pub/sub features in redis use named "channels"; channels do not need to be defined in advance on the server (an interesting use here is things like per-user notification channels, which is what drives parts of the realtime updates on [Stack Overflow](http://stackoverflow.com)). As is common in .NET, subscriptions take the form of callback delegates which accept the channel-name and the message:
sub.Subscribe("messages", (channel, message) => {
```C#
sub.Subscribe("messages", (channel, message) => {
Console.WriteLine((string)message);
});
});
```
Separately (and often in a separate process on a separate machine) you can publish to this channel:
sub.Publish("messages", "hello");
```C#
sub.Publish("messages", "hello");
```
This will (virtually instantaneously) write `"hello"` to the console of the subscribed process. As before, both channel-names and messages can be binary.
...
...
@@ -80,16 +98,22 @@ Accessing individual servers
For maintenance purposes, it is sometimes necessary to issue server-specific commands:
IServer server = redis.GetServer("localhost", 6379);
```C#
IServer server = redis.GetServer("localhost", 6379);
```
The `GetServer` method will accept an [`EndPoint`](http://msdn.microsoft.com/en-us/library/system.net.endpoint(v=vs.110).aspx) or the name/value pair that uniquely identify the server. As before, the object returned from `GetServer` is a cheap pass-thru object that does not need to be stored, and async-state can be optionally specified. Note that the set of available endpoints is also available:
EndPoint[] endpoints = redis.GetEndPoints();
```C#
EndPoint[] endpoints = redis.GetEndPoints();
```
From the `IServer` instance, the [Server commands](http://redis.io/commands#server) are available; for example:
DateTime lastSave = server.LastSave();
ClientInfo[] clients = server.ClientList();
```C#
DateTime lastSave = server.LastSave();
ClientInfo[] clients = server.ClientList();
```
Sync vs Async vs Fire-and-Forget
---
...
...
@@ -107,15 +131,19 @@ The synchronous usage is already shown in the examples above. This is the simple
For asynchronous usage, the key difference is the `Async` suffix on methods, and (typically) the use of the `await` language feature. For example:
string value = "abcdefg";
await db.StringSetAsync("mykey", value);
...
string value = await db.StringGetAsync("mykey");
Console.WriteLine(value); // writes: "abcdefg"
```C#
string value = "abcdefg";
await db.StringSetAsync("mykey", value);
...
string value = await db.StringGetAsync("mykey");
Console.WriteLine(value); // writes: "abcdefg"
```
The fire-and-forget usage is accessed by the optional `CommandFlags flags` parameter on all methods (defaults to none). In this usage, the method returns the default value immediately (so a method that normally returns a `String` will always return `null`, and a method that normally returns an `Int64` will always return `0`). The operation will continue in the background. A typical use-case of this might be to increment page-view counts:
Because there are lots of different ways to configure redis, StackExchange.Redis offers a rich configuration model, which is invoked when calling `Connect` (or `ConnectAsync`):
var conn = ConnectionMultiplexer.Connect(configuration);
```C#
var conn = ConnectionMultiplexer.Connect(configuration);
```
The `configuration` here can be either:
...
...
@@ -17,32 +19,43 @@ Basic Configuration Strings
The *simplest* configuration example is just the host name:
var conn = ConnectionMultiplexer.Connect("localhost");
```C#
var conn = ConnectionMultiplexer.Connect("localhost");
```
This will connect to a single server on the local machine using the default redis port (6379). Additional options are simply appended (comma-delimited). Ports are represented with a colon (`:`) as is usual. Configuration *options* include an `=` after the name. For example:
var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,allowAdmin=true");
```C#
var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,allowAdmin=true");
```
An overview of mapping between the `string` and `ConfigurationOptions` representation is shown below, but you can switch between them trivially:
A common usage is to store the *basic* details in a string, and then apply specific details at runtime:
string configString = GetRedisConfiguration();
var options = ConfigurationOptions.Parse(configString);
options.ClientName = GetAppName(); // only known at runtime
options.AllowAdmin = true;
conn = ConnectionMultiplexer.Connect(options);
```C#
string configString = GetRedisConfiguration();
var options = ConfigurationOptions.Parse(configString);
options.ClientName = GetAppName(); // only known at runtime
options.AllowAdmin = true;
conn = ConnectionMultiplexer.Connect(options);
```
Microsoft Azure Redis example with password
var conn = ConnectionMultiplexer.Connect("contoso5.redis.cache.windows.net,ssl=true,password=...");
```C#
var conn = ConnectionMultiplexer.Connect("contoso5.redis.cache.windows.net,ssl=true,password=...");
```
Configuration Options
---
...
...
@@ -79,8 +92,9 @@ Automatic and Manual Configuration
In many common scenarios, StackExchange.Redis will automatically configure a lot of settings, including the server type and version, connection timeouts, and master/slave relationships. Sometimes, though, the commands for this have been disabled on the redis server. In this case, it is useful to provide more information:
ConfigurationOptions config = new ConfigurationOptions
{
```C#
ConfigurationOptions config = new ConfigurationOptions
{
EndPoints =
{
{ "redis0", 6379 },
...
...
@@ -94,41 +108,49 @@ In many common scenarios, StackExchange.Redis will automatically configure a lot
A slightly unusual feature of redis is that you can disable and/or rename individual commands. As per the previous example, this is done via the `CommandMap`, but instead of passing a `HashSet<string>` to `Create()` (to indicate the available or unavailable commands), you pass a `Dictionary<string,string>`. All commands not mentioned in the dictionary are assumed to be enabled and not renamed. A `null` or blank value records that the command is disabled. For example:
var commands = new Dictionary<string,string> {
```C#
var commands = new Dictionary<string,string> {
{ "info", null }, // disabled
{ "select", "use" }, // renamed to SQL equivalent for some reason
};
var options = new ConfigurationOptions {
};
var options = new ConfigurationOptions {
// ...
CommandMap = CommandMap.Create(commands),
// ...
}
}
```
The above is equivalent to (in the connection string):
$INFO=,$SELECT=use
```config
$INFO=,$SELECT=use
```
Twemproxy
---
[Twemproxy](https://github.com/twitter/twemproxy) is a tool that allows multiple redis instances to be used as though it were a single server, with inbuilt sharding and fault tolerance (much like redis cluster, but implemented separately). The feature-set available to Twemproxy is reduced. To avoid having to configure this manually, the `Proxy` option can be used:
var options = new ConfigurationOptions
{
```C#
var options = new ConfigurationOptions
{
EndPoints = { "my-server" },
Proxy = Proxy.Twemproxy
};
};
```
Tiebreakers and Configuration Change Announcements
// show all keys in database 0 that include "foo" in their name
foreach(var key in server.Keys(pattern: "*foo*")) {
// show all keys in database 0 that include "foo" in their name
foreach(var key in server.Keys(pattern: "*foo*")) {
Console.WriteLine(key);
}
}
// completely wipe ALL keys from database 0
server.FlushDatabase();
// completely wipe ALL keys from database 0
server.FlushDatabase();
```
Note that unlike the `IDatabase` API (where the target database has already been selected in the `GetDatabase()` call), these methods take an optional parameter for the database, or it defaults to `0`.
StackExchange.Redis represents keys by the `RedisKey` type. The good news, though, is that this has implicit conversions to and from both `string` and `byte[]`, allowing both text and binary keys to be used without any complication. For example, the `StringIncrement` method takes a `RedisKey` as the first parameter, but *you don't need to know that*; for example:
string key = ...
db.StringIncrement(key);
```C#
string key = ...
db.StringIncrement(key);
```
or
byte[] key = ...
db.StringIncrement(key);
```C#
byte[] key = ...
db.StringIncrement(key);
```
Likewise, there are operations that *return* keys as `RedisKey` - and again, it simply works:
string someKey = db.KeyRandom();
```C#
string someKey = db.KeyRandom();
```
Values
---
StackExchange.Redis represents values by the `RedisValue` type. As with `RedisKey`, there are implicit conversions in place which mean that most of the time you never see this type, for example:
db.StringSet("mykey", "myvalue");
```C#
db.StringSet("mykey", "myvalue");
```
However, in addition to text and binary contents, values can also need to represent typed primitive data - most commonly (in .NET terms) `Int32`, `Int64`, `Double` or `Boolean`. Because of this, `RedisValue` provides a lot more conversion support than `RedisKey`:
db.StringSet("mykey", 123); // this is still a RedisKey and RedisValue
...
int i = (int)db.StringGet("mykey");
```C#
db.StringSet("mykey", 123); // this is still a RedisKey and RedisValue
...
int i = (int)db.StringGet("mykey");
```
Note that while the conversions from primitives to `RedisValue` are implicit, many of the conversions from `RedisValue` to primitives are explicit: this is because it is very possible that these conversions will fail if the data does not have an appropriate value.
Note additionally that *when treated numerically*, redis treats a non-existent key as zero; for consistency with this, nil responses are treated as zero:
db.KeyDelete("abc");
int i = (int)db.StringGet("abc"); // this is ZERO
```C#
db.KeyDelete("abc");
int i = (int)db.StringGet("abc"); // this is ZERO
```
If you need to detect the nil condition, then you can check for that:
db.KeyDelete("abc");
var value = db.StringGet("abc");
bool isNil = value.IsNull; // this is true
```C#
db.KeyDelete("abc");
var value = db.StringGet("abc");
bool isNil = value.IsNull; // this is true
```
or perhaps more simply, just use the provided `Nullable<T>` support:
db.KeyDelete("abc");
var value = (int?)db.StringGet("abc"); // behaves as you would expect
```C#
db.KeyDelete("abc");
var value = (int?)db.StringGet("abc"); // behaves as you would expect
```
Hashes
---
...
...
@@ -81,14 +97,18 @@ Scripting
Because of this, the `ScriptEvaluate` method accepts two separate input arrays: one `RedisKey[]` for the keys, one `RedisValue[]` for the values (both are optional, and are assumed to be empty if omitted). This is probably one of the few times that you'll actually need to type `RedisKey` or `RedisValue` in your code, and that is just because of array variance rules:
var result = db.ScriptEvaluate(TransferScript,
```C#
var result = db.ScriptEvaluate(TransferScript,
new RedisKey[] { from, to }, new RedisValue[] { quantity });
```
(where `TransferScript` is some `string` containing Lua, not shown for this example)
The response uses the `RedisResult` type (this is unique to scripting; usually the API tries to represent the response as directly and clearly as possible). As before, `RedisResult` offers a range of conversion operations - more, in fact than `RedisValue`, because in addition to being interpreted as text, binary, primitives and nullable-primitives, the response can *also* be interpreted as *arrays* of such, for example:
Latency sucks. Modern computers can churn data at an alarming rate, and high speed networking (often with multiple parallel links between important servers) provides enormous bandwidth, but... that damned latency means that computers spend an awful lot of time *waiting for data* (one of the several reasons that continuation-based programming is becoming increasingly popular. Let's consider some regular procedural code:
string a = db.StringGet("a");
string b = db.StringGet("b");
```C#
string a = db.StringGet("a");
string b = db.StringGet("b");
```
In terms of steps involved, this looks like:
...
...
@@ -40,10 +42,12 @@ Because of this, many redis clients allow you to make use of *pipelining*; this
For example, to pipeline the two gets using procedural (blocking) code, we could use:
var aPending = db.StringGetAsync("a");
var bPending = db.StringGetAsync("b");
var a = db.Wait(aPending);
var b = db.Wait(bPending);
```C#
var aPending = db.StringGetAsync("a");
var bPending = db.StringGetAsync("b");
var a = db.Wait(aPending);
var b = db.Wait(bPending);
```
Note that I'm using `db.Wait` here because it will automatically apply the configured synchronous timeout, but you can use `aPending.Wait()` or `Task.WaitAll(aPending, bPending);` if you prefer. Using pipelining allows us to get both requests onto the network immediately, eliminating most of the latency. Additionally, it also helps reduce packet fragmentation:20 requests sent individually (waiting for each response) will require at least 20 packets, but 20 requests sent in a pipeline could fit into much fewer packets (perhaps even just one).
...
...
@@ -52,9 +56,11 @@ Fire and Forget
A special-case of pipelining is when we expressly don't care about the response from a particular operation, which allows our code to continue immediately while the enqueued operation proceeds in the background. Often, this means that we can put concurrent work on the connection from a single caller. This is achieved using the `flags` parameter:
The `FireAndForget` flag causes the client library to queue the work as normal, but immediately return a default value (since `KeyExpire` returns a `bool`, this will return `false`, because `default(bool)` is `false` - however the return value is meaningless and should be ignored). This works for `*Async` methods too: an already-completed `Task<T>` is returned with the default value (or an already-completed `Task` is returned for `void` methods).
...
...
@@ -65,13 +71,15 @@ Pipelining is all well and good, but often any single block of code only want a
For this reason, the only redis features that StackExchange.Redis does not offer (and *will not ever offer*) are the "blocking pops" ([BLPOP](http://redis.io/commands/blpop), [BRPOP](http://redis.io/commands/brpop) and [BRPOPLPUSH](http://redis.io/commands/brpoplpush)) - because this would allow a single caller to stall the entire multiplexer, blocking all other callers. The only other time that StackExchange.Redis needs to hold work is when verifying pre-conditions for a transaction, which is why StackExchange.Redis encapsulates such conditions into internally managed `Condition` instances. [Read more about transactions here](https://github.com/StackExchange/StackExchange.Redis/blob/master/Docs/Transactions.md). If you feel you want "blocking pops", then I strongly suggest you consider pub/sub instead:
This achieves the same intent without requiring blocking operations. Notes:
...
...
@@ -88,12 +96,14 @@ Concurrency
It should be noted that the pipeline / multiplexer / future-value approach also plays very nicely with continuation-based asynchronous code; for example you could write:
@@ -12,7 +12,9 @@ This works *particularly* well if messages are generally unrelated.
For safety, **the default is sequential**; however, it is strongly recommended that you use concurrent processing whenever possible. This is a simple change:
multiplexer.PreserveAsyncOrder = false;
```C#
multiplexer.PreserveAsyncOrder = false;
```
The reason that this is not a *configuration* option is that whether it is appropriate to do this depends *entirely* on the code that is subscribing to
// ^^^ if true: it was applied; if false: it was rolled back
```
Note that the object returned from `CreateTransaction` only has access to the *async* methods - because the result of
each operation will not be known until after `Execute` (or `ExecuteAsync`) has completed. If the operations are not applied, all the `Task`s
...
...
@@ -82,8 +88,10 @@ Inbuilt operations via `When`
It should also be noted that many common scenarios (in particular: key/hash existence, like in the above) have been anticipated by Redis, and single-operation
atomic commands exist. These are accessed via the `When` parameter - so our previous example can *also* be written as:
var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end",
```C#
var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end",
new RedisKey[] { custKey }, new RedisValue[] { newId });
```
(note that the response from `ScriptEvaluate` and `ScriptEvaluateAsync` is variable depending on your exact script; the response can be interpreted by casting - in this case as a `bool`)
StackExchange.Redis can be installed via the nuget UI (as [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis/)), or via the nuget package manager console:
PM> Install-Package StackExchange.Redis
```PowerShell
PM> Install-Package StackExchange.Redis
```
If you require a strong-named package (because your project is strong-named), then you may wish to use instead: