Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Envoy support for stackexchange #1977

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI
name: CI Builds

on:
pull_request:
Expand All @@ -16,10 +16,11 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v1
- name: Install .NET SDK
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
3.1.x
6.0.x
- name: .NET Build
run: dotnet build Build.csproj -c Release /p:CI=true
Expand All @@ -32,25 +33,24 @@ jobs:
continue-on-error: true
if: success() || failure()
with:
name: Test Results - Ubuntu
name: StackExchange.Redis.Tests (Ubuntu) - Results
path: 'test-results/*.trx'
reporter: dotnet-trx
- name: .NET Lib Pack
run: dotnet pack src/StackExchange.Redis/StackExchange.Redis.csproj --no-build -c Release /p:Packing=true /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true

windows:
name: StackExchange.Redis (Windows Server 2022)
runs-on: windows-2022
env:
NUGET_CERT_REVOCATION_MODE: offline # Disabling signing because of massive perf hit, see https://github.com/NuGet/Home/issues/11548
name: StackExchange.Redis (Windows Server 2019)
runs-on: windows-2019
steps:
- name: Checkout code
uses: actions/checkout@v1
# - name: Install .NET SDK
# uses: actions/setup-dotnet@v1
# with:
# dotnet-version: |
# 6.0.x
- name: Setup .NET Core 3.x
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
3.1.x
6.0.x
- name: .NET Build
run: dotnet build Build.csproj -c Release /p:CI=true
- name: Start Redis Services (v3.0.503)
Expand Down Expand Up @@ -79,6 +79,6 @@ jobs:
continue-on-error: true
if: success() || failure()
with:
name: Tests Results - Windows Server 2022
name: StackExchange.Redis.Tests (Windows Server 2019) - Results
path: 'test-results/*.trx'
reporter: dotnet-trx
reporter: dotnet-trx
2 changes: 2 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ init:

install:
- cmd: >-
choco install dotnet-sdk --version 5.0.404

choco install dotnet-sdk --version 6.0.101

cd tests\RedisConfigs\3.0.503
Expand Down
11 changes: 10 additions & 1 deletion docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ The `ConfigurationOptions` object has a wide range of properties, all of which a
| name={string} | `ClientName` | `null` | Identification for the connection within redis |
| password={string} | `Password` | `null` | Password for the redis server |
| user={string} | `User` | `null` | User for the redis server (for use with ACLs on redis 6 and above) |
| proxy={proxy type} | `Proxy` | `Proxy.None` | Type of proxy in use (if any); for example "twemproxy" |
| proxy={proxy type} | `Proxy` | `Proxy.None` | Type of proxy in use (if any); for example "twemproxy/envoyproxy" |
| resolveDns={bool} | `ResolveDns` | `false` | Specifies that DNS resolution should be explicit and eager, rather than implicit |
| serviceName={string} | `ServiceName` | `null` | Used for connecting to a sentinel master service |
| ssl={bool} | `Ssl` | `false` | Specifies that SSL encryption should be used |
Expand Down Expand Up @@ -178,6 +178,15 @@ var options = new ConfigurationOptions
};
```

[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:

```csharp
var options = new ConfigurationOptions+{
EndPoints = { "my-proxy1", "my-proxy2", "my-proxy3" },
Proxy = Proxy.EnvoyProxy
};
```

Tiebreakers and Configuration Change Announcements
---

Expand Down
43 changes: 0 additions & 43 deletions src/StackExchange.Redis/BacklogPolicy.cs

This file was deleted.

34 changes: 33 additions & 1 deletion src/StackExchange.Redis/CommandMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,38 @@ internal CommandMap(CommandBytes[] map)
RedisCommand.SAVE, RedisCommand.SHUTDOWN, RedisCommand.SLAVEOF, RedisCommand.SLOWLOG, RedisCommand.SYNC, RedisCommand.TIME
});

/// <summary>
/// The commands available to <a href="https://github.com/envoyproxy/envoy">envoyproxy</a> via
/// </summary>
public static CommandMap Envoyproxy { get; } = CreateImpl(null, exclusions: new HashSet<RedisCommand>
{
// not in <a href="https://github.com/envoyproxy/envoy/blob/0fae6970ddaf93f024908ba304bbd2b34e997a51/source/extensions/filters/network/common/redis/supported_commands.h">supported_commands.h</a>
RedisCommand.KEYS, RedisCommand.MIGRATE, RedisCommand.MOVE, RedisCommand.OBJECT, RedisCommand.RANDOMKEY,
RedisCommand.RENAME, RedisCommand.RENAMENX, RedisCommand.SORT, RedisCommand.SCAN,

RedisCommand.BITOP, RedisCommand.MSET, RedisCommand.MSETNX,

RedisCommand.BLPOP, RedisCommand.BRPOP, RedisCommand.BRPOPLPUSH, // yeah, me neither!

RedisCommand.PSUBSCRIBE, RedisCommand.PUBLISH, RedisCommand.PUNSUBSCRIBE, RedisCommand.SUBSCRIBE, RedisCommand.UNSUBSCRIBE,

RedisCommand.DISCARD, RedisCommand.EXEC, RedisCommand.MULTI, RedisCommand.UNWATCH, RedisCommand.WATCH,

RedisCommand.SCRIPT,

RedisCommand.ECHO, RedisCommand.PING, RedisCommand.QUIT, RedisCommand.SELECT,

RedisCommand.BGREWRITEAOF, RedisCommand.BGSAVE, RedisCommand.CLIENT, RedisCommand.CLUSTER, RedisCommand.CONFIG, RedisCommand.DBSIZE,
RedisCommand.DEBUG, RedisCommand.FLUSHALL, RedisCommand.FLUSHDB, RedisCommand.INFO, RedisCommand.LASTSAVE, RedisCommand.MONITOR, RedisCommand.REPLICAOF,
RedisCommand.SAVE, RedisCommand.SHUTDOWN, RedisCommand.SLAVEOF, RedisCommand.SLOWLOG, RedisCommand.SYNC, RedisCommand.TIME,

// supported by envoy but not enabled by stack exchange
// RedisCommand.BITFIELD,
//
// RedisCommand.GEORADIUS_RO,
// RedisCommand.GEORADIUSBYMEMBER_RO,
});

/// <summary>
/// The commands available to <a href="https://ssdb.io/">https://ssdb.io/</a>
/// </summary>
Expand Down Expand Up @@ -173,7 +205,7 @@ internal void AssertAvailable(RedisCommand command)
internal CommandBytes GetBytes(string command)
{
if (command == null) return default;
if(Enum.TryParse(command, true, out RedisCommand cmd))
if (Enum.TryParse(command, true, out RedisCommand cmd))
{ // we know that one!
return map[(int)cmd];
}
Expand Down
13 changes: 1 addition & 12 deletions src/StackExchange.Redis/ConfigurationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,6 @@ public static string TryNormalize(string value)

private IReconnectRetryPolicy reconnectRetryPolicy;

private BacklogPolicy backlogPolicy;

/// <summary>
/// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication; note
/// that this cannot be specified in the configuration-string.
Expand Down Expand Up @@ -275,6 +273,7 @@ public CommandMap CommandMap
get => commandMap ?? Proxy switch
{
Proxy.Twemproxy => CommandMap.Twemproxy,
Proxy.Envoyproxy => CommandMap.Envoyproxy,
_ => CommandMap.Default,
};
set => commandMap = value ?? throw new ArgumentNullException(nameof(value));
Expand Down Expand Up @@ -374,15 +373,6 @@ public IReconnectRetryPolicy ReconnectRetryPolicy
set => reconnectRetryPolicy = value;
}

/// <summary>
/// The backlog policy to be used for commands when a connection is unhealthy.
/// </summary>
public BacklogPolicy BacklogPolicy
{
get => backlogPolicy ?? BacklogPolicy.Default;
set => backlogPolicy = value;
}

/// <summary>
/// Indicates whether endpoints should be resolved via DNS before connecting.
/// If enabled the ConnectionMultiplexer will not re-resolve DNS
Expand Down Expand Up @@ -552,7 +542,6 @@ public ConfigurationOptions Clone()
responseTimeout = responseTimeout,
DefaultDatabase = DefaultDatabase,
ReconnectRetryPolicy = reconnectRetryPolicy,
BacklogPolicy = backlogPolicy,
SslProtocols = SslProtocols,
checkCertificateRevocation = checkCertificateRevocation,
};
Expand Down
87 changes: 52 additions & 35 deletions src/StackExchange.Redis/ConnectionMultiplexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -802,15 +802,15 @@ internal void OnHashSlotMoved(int hashSlot, EndPoint old, EndPoint @new)
/// <param name="key">The key to get a hash slot ID for.</param>
public int HashSlot(RedisKey key) => ServerSelectionStrategy.HashSlot(key);

internal ServerEndPoint AnyServer(ServerType serverType, uint startOffset, RedisCommand command, CommandFlags flags, bool allowDisconnected)
internal ServerEndPoint AnyConnected(ServerType serverType, uint startOffset, RedisCommand command, CommandFlags flags)
{
var tmp = GetServerSnapshot();
int len = tmp.Length;
ServerEndPoint fallback = null;
for (int i = 0; i < len; i++)
{
var server = tmp[(int)(((uint)i + startOffset) % len)];
if (server != null && server.ServerType == serverType && server.IsSelectable(command, allowDisconnected))
if (server != null && server.ServerType == serverType && server.IsSelectable(command))
{
if (server.IsReplica)
{
Expand Down Expand Up @@ -1414,7 +1414,14 @@ internal long LastHeartbeatSecondsAgo
/// <param name="asyncState">The async state object to pass to the created <see cref="RedisSubscriber"/>.</param>
public ISubscriber GetSubscriber(object asyncState = null)
{
if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The pub/sub API is not available via twemproxy");
switch (RawConfig.Proxy)
{
case Proxy.Twemproxy:
case Proxy.Envoyproxy:
throw new NotSupportedException($"The pub/sub API is not available via {RawConfig.Proxy}");
case Proxy.None:
break;
}
return new RedisSubscriber(this, asyncState);
}

Expand All @@ -1430,9 +1437,10 @@ internal int ApplyDefaultDatabase(int db)
throw new ArgumentOutOfRangeException(nameof(db));
}

if (db != 0 && RawConfig.Proxy == Proxy.Twemproxy)
if (db != 0 &&
(RawConfig.Proxy == Proxy.Twemproxy || RawConfig.Proxy == Proxy.Envoyproxy))
{
throw new NotSupportedException("Twemproxy only supports database 0");
throw new NotSupportedException($"{RawConfig.Proxy} only supports database 0");
}

return db;
Expand Down Expand Up @@ -1500,7 +1508,10 @@ public IDatabase GetDatabase(int db = -1, object asyncState = null)
public IServer GetServer(EndPoint endpoint, object asyncState = null)
{
if (endpoint == null) throw new ArgumentNullException(nameof(endpoint));
if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The server API is not available via twemproxy");
if (RawConfig.Proxy == Proxy.Twemproxy || RawConfig.Proxy == Proxy.Envoyproxy)
{
throw new NotSupportedException($"The server API is not available via {RawConfig.Proxy}");
}
var server = (ServerEndPoint)servers[endpoint];
if (server == null) throw new ArgumentException("The specified endpoint is not defined", nameof(endpoint));
return new RedisServer(this, server, asyncState);
Expand Down Expand Up @@ -1816,6 +1827,7 @@ internal async Task<bool> ReconfigureAsync(bool first, bool reconfigureAll, LogP
switch (server.ServerType)
{
case ServerType.Twemproxy:
case ServerType.Envoyproxy:
case ServerType.Sentinel:
case ServerType.Standalone:
case ServerType.Cluster:
Expand Down Expand Up @@ -1860,31 +1872,44 @@ internal async Task<bool> ReconfigureAsync(bool first, bool reconfigureAll, LogP
if (clusterCount == 0)
{
// set the serverSelectionStrategy
if (RawConfig.Proxy == Proxy.Twemproxy)
{
ServerSelectionStrategy.ServerType = ServerType.Twemproxy;
}
else if (standaloneCount == 0 && sentinelCount > 0)
switch (RawConfig.Proxy)
{
ServerSelectionStrategy.ServerType = ServerType.Sentinel;
}
else if (standaloneCount > 0)
{
ServerSelectionStrategy.ServerType = ServerType.Standalone;
case Proxy.Twemproxy:
ServerSelectionStrategy.ServerType = ServerType.Twemproxy;
break;
case Proxy.Envoyproxy:
ServerSelectionStrategy.ServerType = ServerType.Envoyproxy;
break;
default:
if (standaloneCount == 0 && sentinelCount > 0)
{
ServerSelectionStrategy.ServerType = ServerType.Sentinel;
}
else
{
ServerSelectionStrategy.ServerType = ServerType.Standalone;
}
break;
}

var preferred = NominatePreferredMaster(log, servers, useTieBreakers, masters);
foreach (var master in masters)
// if multiple masters are detected nominate preferrered master
// does not apply to envoyproxy, envoyproxy will have multiple "masters" and we would
// we want to keep the default round robin behavior
if (ServerSelectionStrategy.ServerType != ServerType.Envoyproxy)
{
if (master == preferred || master.IsReplica)
{
log?.WriteLine($"{Format.ToString(master)}: Clearing as RedundantMaster");
master.ClearUnselectable(UnselectableFlags.RedundantMaster);
}
else
var preferred = NominatePreferredMaster(log, servers, useTieBreakers, masters);
foreach (var master in masters)
{
log?.WriteLine($"{Format.ToString(master)}: Setting as RedundantMaster");
master.SetUnselectable(UnselectableFlags.RedundantMaster);
if (master == preferred || master.IsReplica)
{
log?.WriteLine($"{Format.ToString(master)}: Clearing as RedundantMaster");
master.ClearUnselectable(UnselectableFlags.RedundantMaster);
}
else
{
log?.WriteLine($"{Format.ToString(master)}: Setting as RedundantMaster");
master.SetUnselectable(UnselectableFlags.RedundantMaster);
}
}
}
}
Expand Down Expand Up @@ -2167,12 +2192,6 @@ private bool PrepareToPushMessageToBridge<T>(Message message, ResultProcessor<T>
{
// Infer a server automatically
server = SelectServer(message);

// If we didn't find one successfully, and we're allowed, queue for any viable server
if (server == null && message != null && RawConfig.BacklogPolicy.QueueWhileDisconnected)
{
server = ServerSelectionStrategy.Select(message, allowDisconnected: true);
}
}
else // a server was specified; do we trust their choice, though?
{
Expand All @@ -2190,9 +2209,7 @@ private bool PrepareToPushMessageToBridge<T>(Message message, ResultProcessor<T>
}
break;
}

// If we're not allowed to queue while disconnected, we'll bomb out below.
if (!server.IsConnected && !RawConfig.BacklogPolicy.QueueWhileDisconnected)
if (!server.IsConnected)
{
// well, that's no use!
server = null;
Expand Down
2 changes: 1 addition & 1 deletion src/StackExchange.Redis/Enums/CommandFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public enum CommandFlags
/// </summary>
NoScriptCache = 512,

// 1024: Removed - was used for async timeout checks; never user-specified, so not visible on the public API
// 1024: used for timed-out; never user-specified, so not visible on the public API

// 2048: Use subscription connection type; never user-specified, so not visible on the public API
}
Expand Down
Loading