1- // internal/store/redis.go
1+ package store
2+
3+ import (
4+ "context"
5+ "time"
6+
7+ "github.com/redis/go-redis/v9"
8+ "github.com/Pavan-Rana/redis-rate-limiter/internal/limiter"
9+ )
10+
11+ type RedisStore struct {
12+ client * redis.Client
13+ scripts * LuaScripts
14+ }
15+
16+ func NewRedisStore (addr string ) (* RedisStore , error ) {
17+ client := redis .NewClient (& redis.Options {Addr : addr })
18+ ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
19+ defer cancel ()
20+
21+ if err := client .Ping (ctx ).Err (); err != nil {
22+ return nil , err
23+ }
24+
25+ scripts , err := LoadScripts (ctx , client )
26+ if err != nil {
27+ return nil , err
28+ }
29+
30+ return & RedisStore {client : client , scripts : scripts }, nil
31+ }
32+
33+ // Single round-trip; no TOCTOU race possible across replicas.
34+ func (r * RedisStore ) AllowRequest (ctx context.Context , key string , policy limiter.Policy ) (bool , error ) {
35+ nowMs := time .Now ().UnixMilli ()
36+ windowMs := policy .Window .Milliseconds ()
37+
38+ result , err := r .client .EvalSha (ctx , r .scripts .SlidingWindowSHA , []string {key },
39+ nowMs , windowMs , policy .Limit
40+ ).Int ()
41+
42+ if err != nil {
43+ return false , err
44+ }
45+
46+ return result == 1 , nil
47+ }
48+
49+ func (r * RedisStore ) Ping (ctx context.Context ) error {
50+ return r .client .Ping (ctx ).Err ()
51+ }
0 commit comments