Skip to content

Commit 8a3978c

Browse files
committed
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
1 parent 391c7e9 commit 8a3978c

33 files changed

+464
-244
lines changed

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

Lines changed: 6 additions & 5 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,7 +26,7 @@ public static IClientBuilder UseRedisClustering(this IClientBuilder builder, Act
2526
}
2627

2728
services
28-
.AddRedis()
29+
.AddRedisClustering()
2930
.AddSingleton<IGatewayListProvider, RedisGatewayListProvider>();
3031
});
3132
}
@@ -38,10 +39,10 @@ public static IClientBuilder UseRedisClustering(this IClientBuilder builder, str
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);
43+
opt.ConfigurationOptions.DefaultDatabase = db;
4344
})
44-
.AddRedis()
45+
.AddRedisClustering()
4546
.AddSingleton<IGatewayListProvider, RedisGatewayListProvider>());
4647
}
4748

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

Lines changed: 11 additions & 5 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,7 +24,7 @@ public static ISiloBuilder UseRedisClustering(this ISiloBuilder builder, Action<
2324
services.Configure(configuration);
2425
}
2526

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

@@ -33,13 +34,18 @@ public static ISiloBuilder UseRedisClustering(this ISiloBuilder builder, Action<
3334
public static ISiloBuilder UseRedisClustering(this ISiloBuilder builder, string redisConnectionString, int db = 0)
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+
options.ConfigurationOptions.DefaultDatabase = db;
41+
})
42+
.AddRedisClustering());
3843
}
3944

40-
internal static IServiceCollection AddRedis(this IServiceCollection services)
45+
internal static IServiceCollection AddRedisClustering(this IServiceCollection services)
4146
{
4247
services.AddSingleton<RedisMembershipTable>();
48+
services.AddSingleton<IConfigurationValidator, RedisClusteringOptionsValidator>();
4349
services.AddSingleton<IMembershipTable>(sp => sp.GetRequiredService<RedisMembershipTable>());
4450
return services;
4551
}

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 {nameof(RedisClusteringOptions)} values for {nameof(RedisMembershipTable)}. {nameof(_options.ConfigurationOptions)} is required.");
62+
}
3363
}
3464
}
3565
}

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

Lines changed: 4 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,12 @@ 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+
await _db.KeyExpireAsync(_clusterKey, _redisOptions.EntryExpiry);
4849
}
4950

5051
this.IsInitialized = true;

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

Lines changed: 7 additions & 4 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,

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

Lines changed: 21 additions & 6 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,51 @@ 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;
3448

3549
public RedisGrainDirectoryOptionsValidator(RedisGrainDirectoryOptions options)
3650
{
37-
this.options = options;
51+
_options = options;
3852
}
3953

54+
/// <inheritdoc/>
4055
public void ValidateConfiguration()
4156
{
42-
if (this.options.ConfigurationOptions == null)
57+
if (_options.ConfigurationOptions == null)
4358
{
44-
throw new OrleansConfigurationException($"Invalid {nameof(RedisGrainDirectoryOptions)} values for {nameof(RedisGrainDirectory)}. {nameof(options.ConfigurationOptions)} is required.");
59+
throw new OrleansConfigurationException($"Invalid {nameof(RedisGrainDirectoryOptions)} values for {nameof(RedisGrainDirectory)}. {nameof(_options.ConfigurationOptions)} is required.");
4560
}
4661
}
4762
}

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")]

src/Redis/Orleans.GrainDirectory.Redis/RedisGrainDirectory.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Text;
34
using System.Text.Json;
45
using System.Threading;
56
using System.Threading.Tasks;
@@ -16,8 +17,9 @@ public class RedisGrainDirectory : IGrainDirectory, ILifecycleParticipant<ISiloL
1617
private readonly RedisGrainDirectoryOptions directoryOptions;
1718
private readonly ClusterOptions clusterOptions;
1819
private readonly ILogger<RedisGrainDirectory> logger;
20+
private readonly RedisKey _keyPrefix;
1921

20-
private ConnectionMultiplexer redis;
22+
private IConnectionMultiplexer redis;
2123
private IDatabase database;
2224
private LuaScript deleteScript;
2325

@@ -29,6 +31,7 @@ public RedisGrainDirectory(
2931
this.directoryOptions = directoryOptions;
3032
this.logger = logger;
3133
this.clusterOptions = clusterOptions.Value;
34+
_keyPrefix = Encoding.UTF8.GetBytes($"{this.clusterOptions.ClusterId}/directory/");
3235
}
3336

3437
public async Task<GrainAddress> Lookup(GrainId grainId)
@@ -121,7 +124,7 @@ public void Participate(ISiloLifecycle lifecycle)
121124

122125
public async Task Initialize(CancellationToken ct = default)
123126
{
124-
this.redis = await ConnectionMultiplexer.ConnectAsync(this.directoryOptions.ConfigurationOptions);
127+
this.redis = await directoryOptions.CreateMultiplexer(directoryOptions);
125128

126129
// Configure logging
127130
this.redis.ConnectionRestored += this.LogConnectionRestored;
@@ -132,16 +135,16 @@ public async Task Initialize(CancellationToken ct = default)
132135
this.database = this.redis.GetDatabase();
133136

134137
this.deleteScript = LuaScript.Prepare(
135-
@"
136-
local cur = redis.call('GET', @key)
137-
if cur ~= false then
138-
local typedCur = cjson.decode(cur)
139-
if typedCur.ActivationId == @val then
140-
return redis.call('DEL', @key)
141-
end
142-
end
143-
return 0
144-
");
138+
"""
139+
local cur = redis.call('GET', @key)
140+
if cur ~= false then
141+
local typedCur = cjson.decode(cur)
142+
if typedCur.ActivationId == @val then
143+
return redis.call('DEL', @key)
144+
end
145+
end
146+
return 0
147+
""");
145148
}
146149

147150
private async Task Uninitialize(CancellationToken arg)
@@ -155,7 +158,7 @@ private async Task Uninitialize(CancellationToken arg)
155158
}
156159
}
157160

158-
private string GetKey(GrainId grainId) => $"{this.clusterOptions.ClusterId}-{grainId}";
161+
private string GetKey(GrainId grainId) => _keyPrefix.Append(grainId.ToString());
159162

160163
#region Logging
161164
private void LogConnectionRestored(object sender, ConnectionFailedEventArgs e)

0 commit comments

Comments
 (0)