Skip to content

Commit da4bf62

Browse files
authored
Various improvements to Redis providers (#8261)
* Various improvements to Redis providers * Use consistent configuration pattern * Add consistent configuration validators * Use unique key prefixes for all providers * Add expiry to all keys for testing * Consistently throw serializable exceptions * Update doc comments * Mark classes which do not need to be public as internal * Other minor cleanup * Review feedback * Convert RedisReminderTable.UpsertRow implementation to Lua script instead of Redis transaction * Fixes for RedisGrainStorage
1 parent bcc52fd commit da4bf62

34 files changed

+513
-326
lines changed

src/Redis/Orleans.Clustering.Redis/Hosting/HostingExtensions.ICientBuilder.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
using System;
1+
using System;
22
using Orleans;
33
using Microsoft.Extensions.DependencyInjection;
44
using Orleans.Hosting;
55
using Orleans.Messaging;
66
using Orleans.Clustering.Redis;
7+
using StackExchange.Redis;
78

89
namespace Microsoft.Extensions.Hosting
910
{
@@ -25,23 +26,22 @@ public static IClientBuilder UseRedisClustering(this IClientBuilder builder, Act
2526
}
2627

2728
services
28-
.AddRedis()
29+
.AddRedisClustering()
2930
.AddSingleton<IGatewayListProvider, RedisGatewayListProvider>();
3031
});
3132
}
3233

3334
/// <summary>
3435
/// Configures Redis as the clustering provider.
3536
/// </summary>
36-
public static IClientBuilder UseRedisClustering(this IClientBuilder builder, string redisConnectionString, int db = 0)
37+
public static IClientBuilder UseRedisClustering(this IClientBuilder builder, string redisConnectionString)
3738
{
3839
return builder.ConfigureServices(services => services
3940
.Configure<RedisClusteringOptions>(opt =>
4041
{
41-
opt.ConnectionString = redisConnectionString;
42-
opt.Database = db;
42+
opt.ConfigurationOptions = ConfigurationOptions.Parse(redisConnectionString);
4343
})
44-
.AddRedis()
44+
.AddRedisClustering()
4545
.AddSingleton<IGatewayListProvider, RedisGatewayListProvider>());
4646
}
4747

src/Redis/Orleans.Clustering.Redis/Hosting/HostingExtensions.ISiloBuilder.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using System;
1+
using System;
22
using Orleans;
33
using Microsoft.Extensions.DependencyInjection;
44
using Orleans.Hosting;
55
using Orleans.Clustering.Redis;
6+
using StackExchange.Redis;
67

78
namespace Microsoft.Extensions.Hosting
89
{
@@ -23,23 +24,27 @@ public static ISiloBuilder UseRedisClustering(this ISiloBuilder builder, Action<
2324
services.Configure(configuration);
2425
}
2526

26-
services.AddRedis();
27+
services.AddRedisClustering();
2728
});
2829
}
2930

3031
/// <summary>
3132
/// Configures Redis as the clustering provider.
3233
/// </summary>
33-
public static ISiloBuilder UseRedisClustering(this ISiloBuilder builder, string redisConnectionString, int db = 0)
34+
public static ISiloBuilder UseRedisClustering(this ISiloBuilder builder, string redisConnectionString)
3435
{
3536
return builder.ConfigureServices(services => services
36-
.Configure<RedisClusteringOptions>(options => { options.Database = db; options.ConnectionString = redisConnectionString; })
37-
.AddRedis());
37+
.Configure<RedisClusteringOptions>(options =>
38+
{
39+
options.ConfigurationOptions = ConfigurationOptions.Parse(redisConnectionString);
40+
})
41+
.AddRedisClustering());
3842
}
3943

40-
internal static IServiceCollection AddRedis(this IServiceCollection services)
44+
internal static IServiceCollection AddRedisClustering(this IServiceCollection services)
4145
{
4246
services.AddSingleton<RedisMembershipTable>();
47+
services.AddSingleton<IConfigurationValidator, RedisClusteringOptionsValidator>();
4348
services.AddSingleton<IMembershipTable>(sp => sp.GetRequiredService<RedisMembershipTable>());
4449
return services;
4550
}

src/Redis/Orleans.Clustering.Redis/Orleans.Clustering.Redis.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<PackageTags>$(PackageTags) Redis Clustering</PackageTags>
88
<TargetFrameworks>$(DefaultTargetFrameworks)</TargetFrameworks>
99
<VersionSuffix Condition="$(VersionSuffix) == ''">beta1</VersionSuffix>
10+
<OrleansBuildTimeCodeGen>true</OrleansBuildTimeCodeGen>
1011
</PropertyGroup>
1112

1213
<ItemGroup>
Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Orleans.Runtime;
12
using StackExchange.Redis;
23
using System;
34
using System.Threading.Tasks;
@@ -10,26 +11,55 @@ namespace Orleans.Clustering.Redis
1011
public class RedisClusteringOptions
1112
{
1213
/// <summary>
13-
/// Specifies the database identi
14+
/// Gets or sets the Redis client configuration.
1415
/// </summary>
15-
public int Database { get; set; }
16+
[RedactRedisConfigurationOptions]
17+
public ConfigurationOptions ConfigurationOptions { get; set; }
1618

1719
/// <summary>
18-
/// The connection string.
20+
/// The delegate used to create a Redis connection multiplexer.
1921
/// </summary>
20-
public string ConnectionString { get; set; } = "localhost:6379";
22+
public Func<RedisClusteringOptions, Task<IConnectionMultiplexer>> CreateMultiplexer { get; set; } = DefaultCreateMultiplexer;
2123

2224
/// <summary>
23-
/// The delegate used to create a Redis connection multiplexer.
25+
/// Entry expiry, null by default. A value should be set ONLY for ephemeral environments (like in tests).
26+
/// Setting a value different from null will cause entries to be deleted after some period of time.
2427
/// </summary>
25-
public Func<RedisClusteringOptions, Task<IConnectionMultiplexer>> CreateMultiplexer { get; set; } = DefaultCreateMultiplexer;
28+
public TimeSpan? EntryExpiry { get; set; } = null;
2629

2730
/// <summary>
2831
/// The default multiplexer creation delegate.
2932
/// </summary>
3033
public static async Task<IConnectionMultiplexer> DefaultCreateMultiplexer(RedisClusteringOptions options)
3134
{
32-
return await ConnectionMultiplexer.ConnectAsync(options.ConnectionString);
35+
return await ConnectionMultiplexer.ConnectAsync(options.ConfigurationOptions);
36+
}
37+
}
38+
39+
internal class RedactRedisConfigurationOptions : RedactAttribute
40+
{
41+
public override string Redact(object value) => value is ConfigurationOptions cfg ? cfg.ToString(includePassword: false) : base.Redact(value);
42+
}
43+
44+
/// <summary>
45+
/// Configuration validator for <see cref="RedisClusteringOptions"/>.
46+
/// </summary>
47+
public class RedisClusteringOptionsValidator : IConfigurationValidator
48+
{
49+
private readonly RedisClusteringOptions _options;
50+
51+
public RedisClusteringOptionsValidator(RedisClusteringOptions options)
52+
{
53+
_options = options;
54+
}
55+
56+
/// <inheritdoc/>
57+
public void ValidateConfiguration()
58+
{
59+
if (_options.ConfigurationOptions == null)
60+
{
61+
throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisMembershipTable)}. {nameof(RedisClusteringOptions)}.{nameof(_options.ConfigurationOptions)} is required.");
62+
}
3363
}
3464
}
3565
}

src/Redis/Orleans.Clustering.Redis/Storage/RedisMembershipTable.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
using Newtonsoft.Json;
77
using System.Linq;
88
using Microsoft.Extensions.Options;
9-
using System.Runtime.CompilerServices;
109
using System.Globalization;
10+
using System.Text;
1111

1212
namespace Orleans.Clustering.Redis
1313
{
@@ -26,7 +26,7 @@ public RedisMembershipTable(IOptions<RedisClusteringOptions> redisOptions, IOpti
2626
{
2727
_redisOptions = redisOptions.Value;
2828
_clusterOptions = clusterOptions.Value;
29-
_clusterKey = $"{_clusterOptions.ServiceId}/{_clusterOptions.ClusterId}";
29+
_clusterKey = Encoding.UTF8.GetBytes($"{_clusterOptions.ServiceId}/members/{_clusterOptions.ClusterId}");
3030
_jsonSerializerSettings = JsonSettings.JsonSerializerSettings;
3131
}
3232

@@ -40,11 +40,16 @@ public async Task DeleteMembershipTableEntries(string clusterId)
4040
public async Task InitializeMembershipTable(bool tryInitTableVersion)
4141
{
4242
_muxer = await _redisOptions.CreateMultiplexer(_redisOptions);
43-
_db = _muxer.GetDatabase(_redisOptions.Database);
43+
_db = _muxer.GetDatabase();
4444

4545
if (tryInitTableVersion)
4646
{
4747
await _db.HashSetAsync(_clusterKey, TableVersionKey, SerializeVersion(DefaultTableVersion), When.NotExists);
48+
49+
if (_redisOptions.EntryExpiry is { } expiry)
50+
{
51+
await _db.KeyExpireAsync(_clusterKey, expiry);
52+
}
4853
}
4954

5055
this.IsInitialized = true;

src/Redis/Orleans.GrainDirectory.Redis/Hosting/RedisGrainDirectoryExtensions.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88

99
namespace Orleans.Hosting
1010
{
11+
/// <summary>
12+
/// Extensions for configuring Redis as a grain directory provider.
13+
/// </summary>
1114
public static class RedisGrainDirectoryExtensions
1215
{
1316
/// <summary>
14-
/// Use a Redis data-store as the default Grain Directory
17+
/// Adds a default grain directory which persists entries in Redis.
1518
/// </summary>
1619
public static ISiloBuilder UseRedisGrainDirectoryAsDefault(
1720
this ISiloBuilder builder,
@@ -21,7 +24,7 @@ public static ISiloBuilder UseRedisGrainDirectoryAsDefault(
2124
}
2225

2326
/// <summary>
24-
/// Use a Redis data-store as the default Grain Directory
27+
/// Adds a default grain directory which persists entries in Redis.
2528
/// </summary>
2629
public static ISiloBuilder UseRedisGrainDirectoryAsDefault(
2730
this ISiloBuilder builder,
@@ -31,7 +34,7 @@ public static ISiloBuilder UseRedisGrainDirectoryAsDefault(
3134
}
3235

3336
/// <summary>
34-
/// Add a Redis data-store as a named Grain Directory
37+
/// Adds a named grain directory which persists entries in Redis.
3538
/// </summary>
3639
public static ISiloBuilder AddRedisGrainDirectory(
3740
this ISiloBuilder builder,
@@ -42,7 +45,7 @@ public static ISiloBuilder AddRedisGrainDirectory(
4245
}
4346

4447
/// <summary>
45-
/// Add a Redis data-store as a named Grain Directory
48+
/// Adds a named grain directory which persists entries in Redis.
4649
/// </summary>
4750
public static ISiloBuilder AddRedisGrainDirectory(
4851
this ISiloBuilder builder,
@@ -59,7 +62,7 @@ private static IServiceCollection AddRedisGrainDirectory(
5962
{
6063
configureOptions.Invoke(services.AddOptions<RedisGrainDirectoryOptions>(name));
6164
services
62-
.AddTransient<IConfigurationValidator>(sp => new RedisGrainDirectoryOptionsValidator(sp.GetRequiredService<IOptionsMonitor<RedisGrainDirectoryOptions>>().Get(name)))
65+
.AddTransient<IConfigurationValidator>(sp => new RedisGrainDirectoryOptionsValidator(sp.GetRequiredService<IOptionsMonitor<RedisGrainDirectoryOptions>>().Get(name), name))
6366
.ConfigureNamedOptionForLogging<RedisGrainDirectoryOptions>(name)
6467
.AddSingletonNamedService<IGrainDirectory>(name, (sp, name) => ActivatorUtilities.CreateInstance<RedisGrainDirectory>(sp, sp.GetOptionsByName<RedisGrainDirectoryOptions>(name)))
6568
.AddSingletonNamedService<ILifecycleParticipant<ISiloLifecycle>>(name, (s, n) => (ILifecycleParticipant<ISiloLifecycle>)s.GetRequiredServiceByName<IGrainDirectory>(n));

src/Redis/Orleans.GrainDirectory.Redis/Options/RedisGrainDirectoryOptions.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading.Tasks;
23
using Orleans.GrainDirectory.Redis;
34
using Orleans.Runtime;
45
using StackExchange.Redis;
@@ -11,37 +12,53 @@ namespace Orleans.Configuration
1112
public class RedisGrainDirectoryOptions
1213
{
1314
/// <summary>
14-
/// Configure the Redis client
15+
/// Gets or sets the Redis client configuration.
1516
/// </summary>
1617
[RedactRedisConfigurationOptions]
1718
public ConfigurationOptions ConfigurationOptions { get; set; }
1819

20+
/// <summary>
21+
/// The delegate used to create a Redis connection multiplexer.
22+
/// </summary>
23+
public Func<RedisGrainDirectoryOptions, Task<IConnectionMultiplexer>> CreateMultiplexer { get; set; } = DefaultCreateMultiplexer;
24+
1925
/// <summary>
2026
/// Entry expiry, null by default. A value should be set ONLY for ephemeral environments (like in tests).
2127
/// Setting a value different from null will cause duplicate activations in the cluster.
2228
/// </summary>
2329
public TimeSpan? EntryExpiry { get; set; } = null;
30+
31+
/// <summary>
32+
/// The default multiplexer creation delegate.
33+
/// </summary>
34+
public static async Task<IConnectionMultiplexer> DefaultCreateMultiplexer(RedisGrainDirectoryOptions options) => await ConnectionMultiplexer.ConnectAsync(options.ConfigurationOptions);
2435
}
2536

26-
public class RedactRedisConfigurationOptions : RedactAttribute
37+
internal class RedactRedisConfigurationOptions : RedactAttribute
2738
{
2839
public override string Redact(object value) => value is ConfigurationOptions cfg ? cfg.ToString(includePassword: false) : base.Redact(value);
2940
}
3041

42+
/// <summary>
43+
/// Configuration validator for <see cref="RedisGrainDirectoryOptions"/>.
44+
/// </summary>
3145
public class RedisGrainDirectoryOptionsValidator : IConfigurationValidator
3246
{
33-
private readonly RedisGrainDirectoryOptions options;
47+
private readonly RedisGrainDirectoryOptions _options;
48+
private readonly string _name;
3449

35-
public RedisGrainDirectoryOptionsValidator(RedisGrainDirectoryOptions options)
50+
public RedisGrainDirectoryOptionsValidator(RedisGrainDirectoryOptions options, string name)
3651
{
37-
this.options = options;
52+
_options = options;
53+
_name = name;
3854
}
3955

56+
/// <inheritdoc/>
4057
public void ValidateConfiguration()
4158
{
42-
if (this.options.ConfigurationOptions == null)
59+
if (_options.ConfigurationOptions == null)
4360
{
44-
throw new OrleansConfigurationException($"Invalid {nameof(RedisGrainDirectoryOptions)} values for {nameof(RedisGrainDirectory)}. {nameof(options.ConfigurationOptions)} is required.");
61+
throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisGrainDirectory)} with name {_name}. {nameof(RedisGrainDirectoryOptions)}.{nameof(_options.ConfigurationOptions)} is required.");
4562
}
4663
}
4764
}

src/Redis/Orleans.GrainDirectory.Redis/Orleans.GrainDirectory.Redis.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<PackageTags>$(PackageTags) Redis Grain Directory</PackageTags>
88
<TargetFrameworks>$(DefaultTargetFrameworks)</TargetFrameworks>
99
<VersionSuffix Condition="$(VersionSuffix) == ''">beta1</VersionSuffix>
10+
<OrleansBuildTimeCodeGen>true</OrleansBuildTimeCodeGen>
1011
</PropertyGroup>
1112

1213
<ItemGroup>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("Tester.Redis")]

0 commit comments

Comments
 (0)