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,56 +92,65 @@ 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
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> {
{ "info", null }, // disabled
{ "select", "use" }, // renamed to SQL equivalent for some reason
};
var options = new ConfigurationOptions {
// ...
CommandMap = CommandMap.Create(commands),
// ...
}
```C#
var commands = new Dictionary<string,string> {
{ "info", null }, // disabled
{ "select", "use" }, // renamed to SQL equivalent for some reason
};
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
{
EndPoints = { "my-server" },
Proxy = Proxy.Twemproxy
};
```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*")) {
Console.WriteLine(key);
}
// completely wipe ALL keys from database 0
server.FlushDatabase();
```C#
// get the target server
var server = conn.GetServer(someServer);
// 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();
```
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,
new RedisKey[] { from, to }, new RedisValue[] { quantity });
```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",
new RedisKey[] { custKey }, new RedisValue[] { newId });
```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: