Skip to content

Commit ed1b107

Browse files
committed
first stab at RESP3; no actual parse code at the moment - this is API-centric
- introduce `Resp2Type` and `Resp3Type` shims (`Resp2Type` has reduced types) - mark existing `Type` as `[Obsolete]`, and proxy to `Resp2Type` for compat - deal with null handling differences - deal with `Boolean`, which works very differently (`t`/`f` instead of `1`/`0`) remove RedisResult.Type from Shipped.txt - handle RESP3 types - handle [+|-]{inf|nan} - avoid alloc when parsing doubles made the RedisResult constructor non-public (fix accidental API) remove RawResult.Type; fix broken loop format incorrect attribute check - nomenclature: MultiBulk => Array - efficiency: use bit-packing to RESP2 type conversion is a bit mask fix RawResult.HasValue fix null array return (EmptyMultiBulk is no longer helpful) add a ToString to Replica (other bits were local test setup issues) simplify switch in TryParseDouble - protocol configuration parsing - avoid inbuilt equality/comparison operations on Version - rules for when to try resp3 configuration documentation words fix ConfigurationOptions.Clone actually connect via RESP3 demand redis6 in the RESP3 connect test remove unnecessary directives Lua results more tests and tweaks to fix tests simplify handshake fallback connect move SETNAME back into HELLO message; add lots of documentation about *why* DEBUG PROTOCOL tests; some failures to look at fix protocol tests add missing "hide me" attribs add docs and release notes tyop redundant re-enable to get server-maintenance notifications - ConnectWithBrokenHello is inconclusive if not a v6 server - allow non-RESP3 tests on non-v6 servers "DEBUG PROTOCOL" tests are inconclusive on non-v6 fix TryConnect (CLIENT ID) not always available save all counting is hard expose IAsyncEnumerable on ChannelMessageQueue (#2402) * expose IAsyncEnumerable on ChannelMessageQueue fix #2400 * PR number * move ChannelMessageQueue.GetAsyncEnumerator to shipped LUA conversions version Lua RESP conversions true/false handling depends on setresp(3) revert "if" split in ResultProcessor use enum for RedisProtocol reinstate parameterless RedisResult .ctor fix resp 2/3 inversion snafu from enumification fix resp dependent connection reuse issue add failing Execute test re RESP2 vs RESP3 delta ValuePairInterleavedProcessorBase should auto-handle responses that have become jagged in RESP3 pattern match is easier to read here - move IsResp3; that is a PhysicalConnection thing, not a ServerEndPoint thing - allow RawResult to know whether it is RESP3; involved moving some flags (which removes a bit hack we were using, so: yay) - make the interleave un-jaggedify only apply on RESP3 add more RESP3 API change tests compensate for XREAD having a different shape in RESP3 disable implicit RESP3 based on target server version # Conflicts: # docs/Configuration.md # docs/ReleaseNotes.md # src/StackExchange.Redis/ConfigurationOptions.cs # src/StackExchange.Redis/ConnectionMultiplexer.cs # src/StackExchange.Redis/Interfaces/IConnectionMultiplexer.cs # src/StackExchange.Redis/ServerEndPoint.cs # tests/StackExchange.Redis.Tests/PubSubTests.cs # tests/StackExchange.Redis.Tests/TestBase.cs
1 parent 2e6b2a8 commit ed1b107

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+2209
-555
lines changed

StackExchange.Redis.sln

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{153A10E4-E
119119
docs\Profiling_v2.md = docs\Profiling_v2.md
120120
docs\PubSubOrder.md = docs\PubSubOrder.md
121121
docs\ReleaseNotes.md = docs\ReleaseNotes.md
122+
docs\Resp3.md = docs\Resp3.md
122123
docs\Scripting.md = docs\Scripting.md
123124
docs\Server.md = docs\Server.md
124125
docs\Testing.md = docs\Testing.md

docs/Configuration.md

+34-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Configuration
1+
# Configuration
22
===
33

44
When connecting to Redis version 6 or above with an ACL configured, your ACL user needs to at least have permissions to run the ECHO command. We run this command to verify that we have a valid connection to the Redis service.
@@ -15,7 +15,7 @@ The `configuration` here can be either:
1515

1616
The latter is *basically* a tokenized form of the former.
1717

18-
Basic Configuration Strings
18+
## Basic Configuration Strings
1919
-
2020

2121
The *simplest* configuration example is just the host name:
@@ -66,7 +66,7 @@ Microsoft Azure Redis example with password
6666
var conn = ConnectionMultiplexer.Connect("contoso5.redis.cache.windows.net,ssl=true,password=...");
6767
```
6868

69-
Configuration Options
69+
## Configuration Options
7070
---
7171

7272
The `ConfigurationOptions` object has a wide range of properties, all of which are fully documented in intellisense. Some of the more common options to use include:
@@ -98,6 +98,7 @@ The `ConfigurationOptions` object has a wide range of properties, all of which a
9898
| version={string} | `DefaultVersion` | (`4.0` in Azure, else `2.0`) | Redis version level (useful when the server does not make this available) |
9999
| tunnel={string} | `Tunnel` | `null` | Tunnel for connections (use `http:{proxy url}` for "connect"-based proxy server) |
100100
| setlib={bool} | `SetClientLibrary` | `true` | Whether to attempt to use `CLIENT SETINFO` to set the library name/version on the connection |
101+
| protocol={string} | `Protocol` | `null` | Redis protocol to use; see section below |
101102

102103
Additional code-only options:
103104
- ReconnectRetryPolicy (`IReconnectRetryPolicy`) - Default: `ReconnectRetryPolicy = ExponentialRetry(ConnectTimeout / 2);`
@@ -121,16 +122,17 @@ Additional code-only options:
121122
Tokens in the configuration string are comma-separated; any without an `=` sign are assumed to be redis server endpoints. Endpoints without an explicit port will use 6379 if ssl is not enabled, and 6380 if ssl is enabled.
122123
Tokens starting with `$` are taken to represent command maps, for example: `$config=cfg`.
123124

124-
Obsolete Configuration Options
125+
## Obsolete Configuration Options
125126
---
127+
126128
These options are parsed in connection strings for backwards compatibility (meaning they do not error as invalid), but no longer have any effect.
127129

128130
| Configuration string | `ConfigurationOptions` | Previous Default | Previous Meaning |
129131
| ---------------------- | ---------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- |
130132
| responseTimeout={int} | `ResponseTimeout` | `SyncTimeout` | Time (ms) to decide whether the socket is unhealthy |
131133
| writeBuffer={int} | `WriteBuffer` | `4096` | Size of the output buffer |
132134

133-
Automatic and Manual Configuration
135+
## Automatic and Manual Configuration
134136
---
135137

136138
In many common scenarios, StackExchange.Redis will automatically configure a lot of settings, including the server type and version, connection timeouts, and primary/replica relationships. Sometimes, though, the commands for this have been disabled on the redis server. In this case, it is useful to provide more information:
@@ -159,7 +161,8 @@ Which is equivalent to the command string:
159161
```config
160162
redis0:6379,redis1:6380,keepAlive=180,version=2.8.8,$CLIENT=,$CLUSTER=,$CONFIG=,$ECHO=,$INFO=,$PING=
161163
```
162-
Renaming Commands
164+
165+
## Renaming Commands
163166
---
164167

165168
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:
@@ -182,8 +185,9 @@ The above is equivalent to (in the connection string):
182185
$INFO=,$SELECT=use
183186
```
184187

185-
Redis Server Permissions
188+
## Redis Server Permissions
186189
---
190+
187191
If the user you're connecting to Redis with is limited, it still needs to have certain commands enabled for the StackExchange.Redis to succeed in connecting. The client uses:
188192
- `AUTH` to authenticate
189193
- `CLIENT` to set the client name
@@ -203,7 +207,7 @@ For example, a common _very_ minimal configuration ACL on the server (non-cluste
203207

204208
Note that if you choose to disable access to the above commands, it needs to be done via the `CommandMap` and not only the ACL on the server (otherwise we'll attempt the command and fail the handshake). Also, if any of the these commands are disabled, some functionality may be diminished or broken.
205209

206-
twemproxy
210+
## twemproxy
207211
---
208212

209213
[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:
@@ -216,8 +220,9 @@ var options = new ConfigurationOptions
216220
};
217221
```
218222

219-
envoyproxy
223+
##envoyproxy
220224
---
225+
221226
[Envoyproxy](https://github.com/envoyproxy/envoy) is a tool that allows to front a redis cluster with a set of proxies, with inbuilt discovery and fault tolerance. The feature-set available to Envoyproxy is reduced. To avoid having to configure this manually, the `Proxy` option can be used:
222227
```csharp
223228
var options = new ConfigurationOptions+{
@@ -227,7 +232,7 @@ var options = new ConfigurationOptions+{
227232
```
228233

229234

230-
Tiebreakers and Configuration Change Announcements
235+
## Tiebreakers and Configuration Change Announcements
231236
---
232237

233238
Normally StackExchange.Redis will resolve primary/replica nodes automatically. However, if you are not using a management tool such as redis-sentinel or redis cluster, there is a chance that occasionally you will get multiple primary nodes (for example, while resetting a node for maintenance it may reappear on the network as a primary). To help with this, StackExchange.Redis can use the notion of a *tie-breaker* - which is only used when multiple primaries are detected (not including redis cluster, where multiple primaries are *expected*). For compatibility with BookSleeve, this defaults to the key named `"__Booksleeve_TieBreak"` (always in database 0). This is used as a crude voting mechanism to help determine the *preferred* primary, so that work is routed correctly.
@@ -238,8 +243,9 @@ Both options can be customized or disabled (set to `""`), via the `.Configuratio
238243

239244
These settings are also used by the `IServer.MakeMaster()` method, which can set the tie-breaker in the database and broadcast the configuration change message. The configuration message can also be used separately to primary/replica changes simply to request all nodes to refresh their configurations, via the `ConnectionMultiplexer.PublishReconfigure` method.
240245

241-
ReconnectRetryPolicy
246+
## ReconnectRetryPolicy
242247
---
248+
243249
StackExchange.Redis automatically tries to reconnect in the background when the connection is lost for any reason. It keeps retrying until the connection has been restored. It would use ReconnectRetryPolicy to decide how long it should wait between the retries.
244250
ReconnectRetryPolicy can be exponential (default), linear or a custom retry policy.
245251

@@ -264,3 +270,20 @@ config.ReconnectRetryPolicy = new LinearRetry(5000);
264270
//5 5000
265271
//6 5000
266272
```
273+
274+
## Redis protocol
275+
```
276+
277+
Without any additional prompting, StackExchange.Redis will use the RESP2 protocol; this means that pub/sub requires a separatate connection to the server. RESP3 is a newer protocol
278+
(usually, but not always, available on v6 servers and above) which allows (smong other changes) pub/sub messages to be communicated on the *same* connection - which can be very
279+
desirable in servers with a large number of clients. The protocol handshake needs to happen very early in the connection, so *by default* the library does not attempt a RESP3 connection
280+
unless it has reason to expect it to work:
281+
282+
This can be considered, in order:
283+
284+
- the `HELLO` command has been disabled: RESP2 is used
285+
- a protocol *other than* `resp3` or `3` is specified: RESP2 is used
286+
- a protocol of `resp3` or `3` is specified: RESP3 is attempted (with fallback if it fails)
287+
- a version of at least 6 is specified: RESP3 is attempted (with fallback if it fails)
288+
- in all other scenarios: RESP2 is used
289+

docs/ReleaseNotes.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Current package versions:
88

99
## Unreleased
1010

11+
- Adds: RESP3 support ([#2396 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2396)) - see https://stackexchange.github.io/StackExchange.Redis/Resp3
1112
- Fix [#2507](https://github.com/StackExchange/StackExchange.Redis/issues/2507): Pub/sub with multi-item payloads should be usable ([#2508 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2508))
1213
- Add: connection-id tracking (internal only, no public API) ([#2508 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2508))
1314

docs/Resp3.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# RESP3 and StackExchange.Redis
2+
3+
RESP2 and RESP3 are evolutions of the Redis protocol; the main differences are:
4+
5+
1. RESP3 can carry out-of-band / "push" messages on a single connection, where-as RESP2 requires a separate connection for these messages
6+
2. RESP3 can (when appropriate) convey additional semantic meaning about returned payloads
7+
8+
For most people, the first point is the main reason to consider RESP3, as in high-usage servers, this can halve the number of connections required.
9+
This is particularly useful in hosted environments where the number of inbound connections to the server is capped as part of a service plan.
10+
Alternatively, where users are currently choosing to disable the out-of-band connection to achieve this, they may now be able to re-enable this
11+
(for example, to receive server maintenance notifications) *without* incurring any additional connection overhead.
12+
13+
There are no significant other differences, i.e. security, performance, etc all perform identically under both RESP2 and RESP3.
14+
15+
RESP3 requires a Redis server version 6 or above; since the library cannot automatically know the server version *before* it has successfully connected,
16+
the library currently requires a hint to enable this mode, in particular, configuring the `ConfigurationOptions.Version` property to 6 (or above), or using
17+
`,version=6.0` (or above) on the configuration string.
18+
19+
When using StackExchange.Redis, the second point only applies to:
20+
21+
- Lua scripts that are invoked via the `ScriptEvaluate[Async](...)` or related APIs, that either:
22+
- uses the `redis.setresp(3)` API and returns a value from `redis.[p]call(...)`
23+
- returns a value that satisfies the [LUA to RESP3 type conversion rules](https://redis.io/docs/manual/programmability/lua-api/#lua-to-resp3-type-conversion)
24+
- ad-hoc commands (in particular: *modules*) that are invoked via the `Execute[Async](string command, ...)` API
25+
26+
both which return `RedisResult`. **If you are not using these APIs, you do not need to do anything.**
27+
28+
Historically, you could use the `RedisResult.Type` property to query the type of data returned (integer, string, etc). In particular:
29+
30+
- two new properties are added: `RedisResult.Resp2Type` and `RedisResult.Resp3Type`
31+
- the `Resp3Type` property exposes the new semantic data (when using RESP3), for example it can indicate that a value is a double-precision number, a boolean, a map, etc (types that did not historically exist)
32+
- the `Resp2Type` property exposes the same value that *would* have been returned if this data had been returned over RESP2
33+
- the `Type` property is now marked obsolete, but functions identically to `Resp2Type`, so that pre-existing code (for example, that has a `switch` on the type) is not impacted by RESP3
34+
- the `ResultType.MultiBulk` is superseded by `ResultType.Array` (this is a nomenclature change only; they are the same value and function identically)
35+
36+
No changes to existing code are *required*, but:
37+
38+
1. to prevent build warnings, replace usage of `ResultType.MultiBulk` with `ResultType.Array`, and usage of `RedisResult.Type` with `RedisResult.Resp2Type`
39+
2. if you wish to exploit the additional semantic data when using RESP3, use `RedisResult.Resp3Type` where appropriate

docs/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Documentation
3939
- [Transactions](Transactions) - how atomic transactions work in redis
4040
- [Events](Events) - the events available for logging / information purposes
4141
- [Pub/Sub Message Order](PubSubOrder) - advice on sequential and concurrent processing
42+
- [Using RESP3](Resp3) - information on using RESP3
4243
- [ServerMaintenanceEvent](ServerMaintenanceEvent) - how to listen and prepare for hosted server maintenance (e.g. Azure Cache for Redis)
4344
- [Streams](Streams) - how to use the Stream data type
4445
- [Where are `KEYS` / `SCAN` / `FLUSH*`?](KeysScan) - how to use server-based commands

src/StackExchange.Redis/APITypes/LatencyHistoryEntry.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ private sealed class Processor : ArrayResultProcessor<LatencyHistoryEntry>
1313
{
1414
protected override bool TryParse(in RawResult raw, out LatencyHistoryEntry parsed)
1515
{
16-
if (raw.Type == ResultType.MultiBulk)
16+
if (raw.Resp2TypeArray == ResultType.Array)
1717
{
1818
var items = raw.GetItems();
1919
if (items.Length >= 2

src/StackExchange.Redis/APITypes/LatencyLatestEntry.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ private sealed class Processor : ArrayResultProcessor<LatencyLatestEntry>
1313
{
1414
protected override bool TryParse(in RawResult raw, out LatencyLatestEntry parsed)
1515
{
16-
if (raw.Type == ResultType.MultiBulk)
16+
if (raw.Resp2TypeArray == ResultType.Array)
1717
{
1818
var items = raw.GetItems();
1919
if (items.Length >= 4

src/StackExchange.Redis/ChannelMessageQueue.cs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Reflection;
4+
using System.Runtime.CompilerServices;
45
using System.Threading;
56
using System.Threading.Channels;
67
using System.Threading.Tasks;

src/StackExchange.Redis/ClientInfo.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ private class ClientInfoProcessor : ResultProcessor<ClientInfo[]>
280280
{
281281
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
282282
{
283-
switch(result.Type)
283+
switch(result.Resp2TypeBulkString)
284284
{
285285
case ResultType.BulkString:
286286
var raw = result.GetString();

src/StackExchange.Redis/CommandTrace.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ private class CommandTraceProcessor : ResultProcessor<CommandTrace[]>
7373
{
7474
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
7575
{
76-
switch(result.Type)
76+
switch(result.Resp2TypeArray)
7777
{
78-
case ResultType.MultiBulk:
78+
case ResultType.Array:
7979
var parts = result.GetItems();
8080
CommandTrace[] arr = new CommandTrace[parts.Length];
8181
int i = 0;

src/StackExchange.Redis/Condition.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ internal override bool TryValidate(in RawResult result, out bool value)
563563
return true;
564564

565565
default:
566-
switch (result.Type)
566+
switch (result.Resp2TypeBulkString)
567567
{
568568
case ResultType.BulkString:
569569
case ResultType.SimpleString:
@@ -619,7 +619,7 @@ internal sealed override IEnumerable<Message> CreateMessages(int db, IResultBox?
619619

620620
internal override bool TryValidate(in RawResult result, out bool value)
621621
{
622-
switch (result.Type)
622+
switch (result.Resp2TypeBulkString)
623623
{
624624
case ResultType.BulkString:
625625
case ResultType.SimpleString:
@@ -692,7 +692,7 @@ internal sealed override IEnumerable<Message> CreateMessages(int db, IResultBox?
692692

693693
internal override bool TryValidate(in RawResult result, out bool value)
694694
{
695-
switch (result.Type)
695+
switch (result.Resp2TypeBulkString)
696696
{
697697
case ResultType.BulkString:
698698
case ResultType.SimpleString:
@@ -749,7 +749,7 @@ internal sealed override IEnumerable<Message> CreateMessages(int db, IResultBox?
749749

750750
internal override bool TryValidate(in RawResult result, out bool value)
751751
{
752-
switch (result.Type)
752+
switch (result.Resp2TypeBulkString)
753753
{
754754
case ResultType.BulkString:
755755
case ResultType.SimpleString:
@@ -806,7 +806,7 @@ internal sealed override IEnumerable<Message> CreateMessages(int db, IResultBox?
806806

807807
internal override bool TryValidate(in RawResult result, out bool value)
808808
{
809-
switch (result.Type)
809+
switch (result.Resp2TypeBulkString)
810810
{
811811
case ResultType.Integer:
812812
var parsedValue = result.AsRedisValue();

0 commit comments

Comments
 (0)