Skip to content

Commit

Permalink
Separate redis-specific logic from generic k/v store logic
Browse files Browse the repository at this point in the history
This is a pure refactoring with no behavior changes as a step toward being able
to add memcache support (see envoyproxy#140).

RateLimitCache moves from the redis package to the new limiter package, along with
code for time/jitter and building cache keys. Those can all be reused for memcache.

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.
  • Loading branch information
dweitzman committed May 27, 2020
1 parent 0dd5b8b commit 7334211
Show file tree
Hide file tree
Showing 16 changed files with 442 additions and 321 deletions.
2 changes: 1 addition & 1 deletion src/redis/cache.go → src/limiter/cache.go
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"
Expand Down
90 changes: 90 additions & 0 deletions src/limiter/cache_key.go
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)}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package redis
package limiter

import (
"github.com/coocood/freecache"
Expand Down
40 changes: 40 additions & 0 deletions src/limiter/time.go
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()
}
Loading

0 comments on commit 7334211

Please sign in to comment.