-
Notifications
You must be signed in to change notification settings - Fork 465
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split redis-specific logic from generic key-value store logic
This is a pure refactoring with no behavior changes. It's a step toward being able to add memcache as a backend (see #140). This PR moves RateLimitCache from the redis package to a new "limiter" package, along with code for time/jitter and for constructing cache keys. All that can be reused with memcache. After this PR, the redis package is imported in exactly two places: - in service_cmd/runner/runner.go to call redis.NewRateLimiterCacheImplFromSettings() - in service/ratelimit.go in ShouldRateLimit to identify if a recovered panic is a redis.RedisError. If so, a stat is incremented and the panic() propagation is ended and in favor of returning the error as a the function result. The PR also includes changes by goimports to test/service/ratelimit_test.go so that the difference between package name vs file path name is explicit instead of implicit. Signed-off-by: David Weitzman <dweitzman@pinterest.com>
- Loading branch information
Showing
16 changed files
with
443 additions
and
321 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
package redis | ||
package limiter | ||
|
||
import ( | ||
pb "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package limiter | ||
|
||
import ( | ||
"bytes" | ||
"strconv" | ||
"sync" | ||
|
||
pb_struct "github.com/envoyproxy/go-control-plane/envoy/api/v2/ratelimit" | ||
pb "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2" | ||
"github.com/envoyproxy/ratelimit/src/config" | ||
) | ||
|
||
type CacheKeyGenerator struct { | ||
// bytes.Buffer pool used to efficiently generate cache keys. | ||
bufferPool sync.Pool | ||
} | ||
|
||
func NewCacheKeyGenerator() CacheKeyGenerator { | ||
return CacheKeyGenerator{bufferPool: sync.Pool{ | ||
New: func() interface{} { | ||
return new(bytes.Buffer) | ||
}, | ||
}} | ||
} | ||
|
||
type CacheKey struct { | ||
Key string | ||
// True if the key corresponds to a limit with a SECOND unit. False otherwise. | ||
PerSecond bool | ||
} | ||
|
||
func isPerSecondLimit(unit pb.RateLimitResponse_RateLimit_Unit) bool { | ||
return unit == pb.RateLimitResponse_RateLimit_SECOND | ||
} | ||
|
||
// Convert a rate limit into a time divider. | ||
// @param unit supplies the unit to convert. | ||
// @return the divider to use in time computations. | ||
func UnitToDivider(unit pb.RateLimitResponse_RateLimit_Unit) int64 { | ||
switch unit { | ||
case pb.RateLimitResponse_RateLimit_SECOND: | ||
return 1 | ||
case pb.RateLimitResponse_RateLimit_MINUTE: | ||
return 60 | ||
case pb.RateLimitResponse_RateLimit_HOUR: | ||
return 60 * 60 | ||
case pb.RateLimitResponse_RateLimit_DAY: | ||
return 60 * 60 * 24 | ||
} | ||
|
||
panic("should not get here") | ||
} | ||
|
||
// Generate a cache key for a limit lookup. | ||
// @param domain supplies the cache key domain. | ||
// @param descriptor supplies the descriptor to generate the key for. | ||
// @param limit supplies the rate limit to generate the key for (may be nil). | ||
// @param now supplies the current unix time. | ||
// @return CacheKey struct. | ||
func (this *CacheKeyGenerator) GenerateCacheKey( | ||
domain string, descriptor *pb_struct.RateLimitDescriptor, limit *config.RateLimit, now int64) CacheKey { | ||
|
||
if limit == nil { | ||
return CacheKey{ | ||
Key: "", | ||
PerSecond: false, | ||
} | ||
} | ||
|
||
b := this.bufferPool.Get().(*bytes.Buffer) | ||
defer this.bufferPool.Put(b) | ||
b.Reset() | ||
|
||
b.WriteString(domain) | ||
b.WriteByte('_') | ||
|
||
for _, entry := range descriptor.Entries { | ||
b.WriteString(entry.Key) | ||
b.WriteByte('_') | ||
b.WriteString(entry.Value) | ||
b.WriteByte('_') | ||
} | ||
|
||
divider := UnitToDivider(limit.Limit.Unit) | ||
b.WriteString(strconv.FormatInt((now/divider)*divider, 10)) | ||
|
||
return CacheKey{ | ||
Key: b.String(), | ||
PerSecond: isPerSecondLimit(limit.Limit.Unit)} | ||
} |
2 changes: 1 addition & 1 deletion
2
src/redis/local_cache_stats.go → src/limiter/local_cache_stats.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
package redis | ||
package limiter | ||
|
||
import ( | ||
"github.com/coocood/freecache" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package limiter | ||
|
||
import ( | ||
"math/rand" | ||
"sync" | ||
"time" | ||
) | ||
|
||
type timeSourceImpl struct{} | ||
|
||
func NewTimeSourceImpl() TimeSource { | ||
return &timeSourceImpl{} | ||
} | ||
|
||
func (this *timeSourceImpl) UnixNow() int64 { | ||
return time.Now().Unix() | ||
} | ||
|
||
// rand for jitter. | ||
type lockedSource struct { | ||
lk sync.Mutex | ||
src rand.Source | ||
} | ||
|
||
func NewLockedSource(seed int64) JitterRandSource { | ||
return &lockedSource{src: rand.NewSource(seed)} | ||
} | ||
|
||
func (r *lockedSource) Int63() (n int64) { | ||
r.lk.Lock() | ||
n = r.src.Int63() | ||
r.lk.Unlock() | ||
return | ||
} | ||
|
||
func (r *lockedSource) Seed(seed int64) { | ||
r.lk.Lock() | ||
r.src.Seed(seed) | ||
r.lk.Unlock() | ||
} |
Oops, something went wrong.