-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCacheRegistration.cs
More file actions
151 lines (138 loc) · 6.9 KB
/
Copy pathCacheRegistration.cs
File metadata and controls
151 lines (138 loc) · 6.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
using Common.Core.Caching.Internals;
using Common.Core.Caching.Options;
using Common.Core.Caching.Services;
using Common.Core.Exceptions.RuntimeExceptions;
using Common.Core.Microservice.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
namespace Common.Core.Caching;
/// <summary>
/// Hybrid Cache Registration
/// </summary>
public static class CacheRegistration
{
/// <param name="services">The service collection to which the Redis cache services will be added.</param>
extension(IServiceCollection services)
{
/// <summary>
/// Adding memory cache related to service.
/// </summary>
public void AddInMemoryCache()
{
services.AddMemoryCache();
services.AddSingleton<ICacheProvider, MemoryCacheProvider>();
}
/// <summary>
/// Registers Redis cache services in the dependency injection container.
/// </summary>
/// <param name="configuration">The application configuration instance used to read Redis settings.</param>
/// <exception cref="ConnectionStringException">Thrown when the connection string for Redis is missing or invalid.</exception>
public async Task AddRedisCacheAsync(IConfiguration configuration)
{
var redisOptions = configuration.GetServiceOptions().RedisOptions;
if (redisOptions.IsSentinelConfig)
{
var redisConfiguration = new ConfigurationOptions
{
ServiceName = redisOptions.MasterName,
Password = redisOptions.Password,
AbortOnConnectFail = false,
AllowAdmin = true,
DefaultDatabase = redisOptions.DatabaseId,
};
foreach (var sentinel in redisOptions.Sentinels) redisConfiguration.EndPoints.Add(sentinel);
var connection = await ConnectionMultiplexer.ConnectAsync(redisConfiguration);
var db = connection.GetDatabase(redisOptions.DatabaseId);
services.AddSingleton<IConnectionMultiplexer>(connection);
services.AddSingleton(db);
}
else
{
if (redisOptions.Password != null)
{
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = $"{redisOptions.ConnectionString},password={redisOptions.Password},defaultDatabase={redisOptions.DatabaseId}";
options.InstanceName = redisOptions.InstanceName;
});
}
else
{
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = $"{redisOptions.ConnectionString},defaultDatabase={redisOptions.DatabaseId}";
options.InstanceName = redisOptions.InstanceName;
});
}
ConfigurationOptions redisConfiguration;
if (redisOptions.Password != null)
{
redisConfiguration = new ConfigurationOptions
{
EndPoints = { redisOptions.ConnectionString },
Password = redisOptions.Password,
AbortOnConnectFail = false,
AllowAdmin = true,
SyncTimeout = 10000,
AsyncTimeout = 10000,
ConnectTimeout = 15000,
};
}
else
{
redisConfiguration = new ConfigurationOptions
{
EndPoints = { redisOptions.ConnectionString },
AbortOnConnectFail = false,
AllowAdmin = true,
SyncTimeout = 10000,
AsyncTimeout = 10000,
ConnectTimeout = 15000,
};
}
var connection = await ConnectionMultiplexer.ConnectAsync(redisConfiguration);
_ = connection.GetDatabase(redisOptions.DatabaseId);
services.AddSingleton<IConnectionMultiplexer>(connection);
services.AddSingleton<IConnectionMultiplexer>(await ConnectionMultiplexer.ConnectAsync(redisConfiguration));
}
services.AddSingleton<IRedisCacheProvider, RedisCacheProvider>();
services.AddSingleton<ICacheProvider, RedisCacheProvider>();
}
/// <summary>
/// Registers and configures the hybrid caching mechanism, which combines in-memory and distributed cache services.
/// This includes options for invalidation synchronization across nodes and centralized configuration for cache settings.
/// </summary>
/// <param name="configuration">Configuration instance to access application settings, including cache settings.</param>
/// <param name="hybridCacheOptions">Optional custom configuration for the hybrid cache behavior.</param>
/// <returns>A task that represents the asynchronous operation of adding the hybrid cache services.</returns>
public async Task AddHybridCache(IConfiguration configuration, HybridCacheOptions? hybridCacheOptions = null)
{
await services.AddRedisCacheAsync(configuration);
services.AddInMemoryCache();
services.Configure<HybridCacheOptions>(options =>
{
if (hybridCacheOptions is not null)
{
options.DefaultMemoryCacheDuration = hybridCacheOptions.DefaultMemoryCacheDuration;
}
});
services.AddSingleton<IHybridCache, HybridCache>();
/*
* Cache Invalidation Background Service
* ---------------------------------------
* This background service is responsible for ensuring cache consistency across distributed
* application nodes by handling cache invalidation in the in-memory cache.
*
* It achieves this by subscribing to a specific Redis channel that broadcasts invalidation messages.
* When a message is received, the service decodes the message to determine the corresponding cache key
* and removes that entry from the local memory cache.
*
* This mechanism ensures that stale data is not served to end users by synchronizing cache state across
* different nodes. Additionally, the service logs any errors encountered during its operation, and it
* captures these exceptions using an external monitoring tool for further analysis.
*/
services.AddHostedService<CacheInvalidationBackgroundService>();
}
}
}