1
1
using StackExchange . Redis ;
2
2
using System ;
3
+ using System . Threading . RateLimiting ;
3
4
using System . Threading . Tasks ;
4
5
5
6
namespace RedisRateLimiting . Concurrency
@@ -9,6 +10,7 @@ internal class RedisSlidingWindowManager
9
10
private readonly IConnectionMultiplexer _connectionMultiplexer ;
10
11
private readonly RedisSlidingWindowRateLimiterOptions _options ;
11
12
private readonly RedisKey RateLimitKey ;
13
+ private readonly RedisKey StatsRateLimitKey ;
12
14
13
15
private static readonly LuaScript _redisScript = LuaScript . Prepare (
14
16
@"local limit = tonumber(@permit_limit)
@@ -28,8 +30,22 @@ internal class RedisSlidingWindowManager
28
30
29
31
redis.call(""expireat"", @rate_limit_key, timestamp + window + 1)
30
32
33
+ if allowed
34
+ then
35
+ redis.call(""hincrby"", @stats_key, 'total_successful', 1)
36
+ else
37
+ redis.call(""hincrby"", @stats_key, 'total_failed', 1)
38
+ end
39
+
31
40
return { allowed, count }" ) ;
32
41
42
+ private static readonly LuaScript StatisticsScript = LuaScript . Prepare (
43
+ @"local count = redis.call(""zcard"", @rate_limit_key)
44
+ local total_successful_count = redis.call(""hget"", @stats_key, 'total_successful')
45
+ local total_failed_count = redis.call(""hget"", @stats_key, 'total_failed')
46
+
47
+ return { count, total_successful_count, total_failed_count }" ) ;
48
+
33
49
public RedisSlidingWindowManager (
34
50
string partitionKey ,
35
51
RedisSlidingWindowRateLimiterOptions options )
@@ -38,6 +54,7 @@ public RedisSlidingWindowManager(
38
54
_connectionMultiplexer = options . ConnectionMultiplexerFactory ! . Invoke ( ) ;
39
55
40
56
RateLimitKey = new RedisKey ( $ "rl:{{{partitionKey}}}") ;
57
+ StatsRateLimitKey = new RedisKey ( $ "rl:{{{partitionKey}}}:stats") ;
41
58
}
42
59
43
60
internal async Task < RedisSlidingWindowResponse > TryAcquireLeaseAsync ( string requestId )
@@ -54,6 +71,7 @@ internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requ
54
71
rate_limit_key = RateLimitKey ,
55
72
permit_limit = _options . PermitLimit ,
56
73
window = _options . Window . TotalSeconds ,
74
+ stats_key = StatsRateLimitKey ,
57
75
current_time = nowUnixTimeSeconds ,
58
76
unique_id = requestId ,
59
77
} ) ;
@@ -83,6 +101,7 @@ internal RedisSlidingWindowResponse TryAcquireLease(string requestId)
83
101
rate_limit_key = RateLimitKey ,
84
102
permit_limit = _options . PermitLimit ,
85
103
window = _options . Window . TotalSeconds ,
104
+ stats_key = StatsRateLimitKey ,
86
105
current_time = nowUnixTimeSeconds ,
87
106
unique_id = requestId ,
88
107
} ) ;
@@ -97,6 +116,31 @@ internal RedisSlidingWindowResponse TryAcquireLease(string requestId)
97
116
98
117
return result ;
99
118
}
119
+
120
+ internal RateLimiterStatistics ? GetStatistics ( )
121
+ {
122
+ var database = _connectionMultiplexer . GetDatabase ( ) ;
123
+
124
+ var response = ( RedisValue [ ] ? ) database . ScriptEvaluate (
125
+ StatisticsScript ,
126
+ new
127
+ {
128
+ rate_limit_key = RateLimitKey ,
129
+ stats_key = StatsRateLimitKey ,
130
+ } ) ;
131
+
132
+ if ( response == null )
133
+ {
134
+ return null ;
135
+ }
136
+
137
+ return new RateLimiterStatistics
138
+ {
139
+ CurrentAvailablePermits = _options . PermitLimit - ( long ) response [ 0 ] ,
140
+ TotalSuccessfulLeases = ( long ) response [ 1 ] ,
141
+ TotalFailedLeases = ( long ) response [ 2 ] ,
142
+ } ;
143
+ }
100
144
}
101
145
102
146
internal class RedisSlidingWindowResponse
0 commit comments