Skip to content

Commit cea496b

Browse files
authored
Merge pull request #2 from PandaTechAM/development
Rate limiting feature implementation redesigned for handling locks an…
2 parents f4ffebe + bd8a9c4 commit cea496b

File tree

4 files changed

+85
-41
lines changed

4 files changed

+85
-41
lines changed

src/DistributedCache/DistributedCache.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
<PackageReadmeFile>Readme.md</PackageReadmeFile>
99
<Authors>Pandatech</Authors>
1010
<Copyright>MIT</Copyright>
11-
<Version>1.1.0</Version>
11+
<Version>1.2.0</Version>
1212
<PackageId>Pandatech.DistributedCache</PackageId>
1313
<Title>Pandatech Distributed Cache</Title>
1414
<PackageTags>Pandatech, library, redis, distributed locks, cache</PackageTags>
1515
<Description>Pandatech.DistributedCache is a comprehensive caching library designed for .NET applications, leveraging the power of Redis. It provides easy-to-use and highly configurable caching mechanisms, including support for tagged cache entries, customizable expiration policies, and robust health check services. The library also features built-in distributed lock mechanisms to ensure data consistency and prevent cache stampedes. This ensures high performance, scalability, and reliability, making it an ideal choice for enterprise-level distributed caching needs.</Description>
1616
<RepositoryUrl>https://github.com/PandaTechAM/be-lib-distributed-cache</RepositoryUrl>
17-
<PackageReleaseNotes>Rate limiting feature</PackageReleaseNotes>
17+
<PackageReleaseNotes>Rate limiting feature implementation redesigned for handling locks and using module prefix</PackageReleaseNotes>
1818
</PropertyGroup>
1919

2020
<ItemGroup>

src/DistributedCache/Dtos/RateLimitCache.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
using DistributedCache.Services.Interfaces;
2-
using MessagePack;
1+
using MessagePack;
32

43
namespace DistributedCache.Dtos;
54

65
[MessagePackObject]
7-
public class RateLimitCache : ICacheEntity
6+
public class RateLimitCache
87
{
98
[Key(0)] public int Attempts { get; set; } = 1;
109
[Key(1)] public int MaxAttempts { get; init; }

src/DistributedCache/Services/Implementations/RedisCacheService.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
using DistributedCache.Dtos;
2-
using DistributedCache.Helpers;
1+
using DistributedCache.Helpers;
32
using DistributedCache.Options;
43
using DistributedCache.Services.Interfaces;
54
using Microsoft.Extensions.Options;
6-
using StackExchange.Redis;
75
using StackExchange.Redis.Extensions.Core.Abstractions;
86

97
namespace DistributedCache.Services.Implementations;
Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,97 @@
1-
using DistributedCache.Dtos;
1+
using System.Reflection;
2+
using DistributedCache.Dtos;
23
using DistributedCache.Enums;
4+
using DistributedCache.Helpers;
5+
using DistributedCache.Options;
36
using DistributedCache.Services.Interfaces;
4-
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Extensions.Options;
8+
using StackExchange.Redis.Extensions.Core.Abstractions;
59

610
namespace DistributedCache.Services.Implementations;
711

8-
public class RedisRateLimitService(ICacheService<RateLimitCache> cacheService, RedisLockService lockService)
9-
: IRateLimitService
12+
public class RedisRateLimitService(
13+
IRedisClient redisClient,
14+
IOptions<CacheConfigurationOptions> options,
15+
RedisLockService lockService) : IRateLimitService
1016
{
11-
public async ValueTask<RateLimitState> RateLimitAsync(RateLimitConfiguration rateLimitConfiguration,
12-
CancellationToken cancellationToken = default)
13-
{
14-
var key = rateLimitConfiguration.GetKey();
15-
16-
var cache = await cacheService.GetAsync(key, cancellationToken);
17-
18-
if (cache == null)
19-
{
17+
private readonly IRedisDatabase _redisDatabase = redisClient.GetDefaultDatabase();
18+
private readonly CacheConfigurationOptions _config = options.Value;
19+
20+
private readonly string _moduleName = Assembly.GetExecutingAssembly()
21+
.FullName!;
22+
23+
public async ValueTask<RateLimitState> RateLimitAsync(RateLimitConfiguration rateLimitConfiguration,
24+
CancellationToken cancellationToken = default)
25+
{
26+
var key = _config.KeyPrefixForIsolation == KeyPrefix.None
27+
? KeyFormatHelper.GetPrefixedKey(rateLimitConfiguration.GetKey())
28+
: KeyFormatHelper.GetPrefixedKey(rateLimitConfiguration.GetKey(), _moduleName);
29+
30+
31+
var lockValue = Guid.NewGuid()
32+
.ToString();
33+
34+
while (true)
35+
{
36+
cancellationToken.ThrowIfCancellationRequested();
37+
38+
var isLocked = await lockService.CheckForLockAsync(key);
39+
40+
if (isLocked)
41+
{
42+
await lockService.WaitForLockReleaseAsync(key, cancellationToken);
43+
continue;
44+
}
45+
46+
var lockAcquired = await lockService.AcquireLockAsync(key, lockValue);
47+
if (!lockAcquired)
48+
{
49+
await lockService.WaitForLockReleaseAsync(key, cancellationToken);
50+
continue;
51+
}
52+
53+
break;
54+
}
55+
56+
try
57+
{
58+
var cache = await _redisDatabase.GetAsync<RateLimitCache>(key);
59+
60+
if (cache is null)
61+
{
2062
var newCache = RateLimitCache.CreateRateLimitCache(rateLimitConfiguration);
21-
await cacheService.SetAsync(key, newCache, rateLimitConfiguration.TimeToLive,null ,cancellationToken);
22-
63+
await _redisDatabase.AddAsync(key, newCache, rateLimitConfiguration.TimeToLive);
64+
2365
return new RateLimitState(
24-
RateLimitStatus.NotExceeded,
25-
rateLimitConfiguration.TimeToLive,
26-
rateLimitConfiguration.MaxAttempts - 1
66+
RateLimitStatus.NotExceeded,
67+
rateLimitConfiguration.TimeToLive,
68+
rateLimitConfiguration.MaxAttempts - 1
2769
);
28-
}
70+
}
2971

30-
var isUpdated = cache.TryUpdateAttempts();
31-
var newExpiration = cache.GetNewExpiration();
72+
var isUpdated = cache.TryUpdateAttempts();
73+
var newExpiration = cache.GetNewExpiration();
3274

33-
if (!isUpdated)
34-
{
75+
if (!isUpdated)
76+
{
3577
return new RateLimitState(
36-
RateLimitStatus.Exceeded,
37-
newExpiration,
38-
0
78+
RateLimitStatus.Exceeded,
79+
newExpiration,
80+
0
3981
);
40-
}
82+
}
4183

42-
await cacheService.SetAsync(key, cache, newExpiration, null, cancellationToken);
84+
await _redisDatabase.AddAsync(key, cache, newExpiration);
4385

44-
return new RateLimitState(
45-
RateLimitStatus.NotExceeded,
46-
newExpiration,
86+
return new RateLimitState(
87+
RateLimitStatus.NotExceeded,
88+
newExpiration,
4789
cache.MaxAttempts - cache.Attempts
48-
);
49-
}
90+
);
91+
}
92+
finally
93+
{
94+
await lockService.ReleaseLockAsync(key, lockValue);
95+
}
96+
}
5097
}

0 commit comments

Comments
 (0)