feat(19.3-01): share rate limits across instances through cooler's Redis store
- new module cooler: Install, Handle, OpenRedis (no URL in errors), RateLimitStore (atomic fixed-window Lua script with MemoryStore semantics, per-instance fallback) - serve installs cooler and publishes the Redis surf.Store before BuildRouter; BuildRouter looks up a published Store, route:list keeps the memory store - config keys http.rate_limit.store and redis.url; go-redis v9.23.0 - cooler README, root table row, surf README and rate-limiting, configuration and scaling docs
This commit is contained in:
46
modules/cooler/redis.go
Normal file
46
modules/cooler/redis.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package cooler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/redis/go-redis/v9/maintnotifications"
|
||||
)
|
||||
|
||||
// errInvalidRedisURL is the only error OpenRedis returns for a URL that does
|
||||
// not parse. It never wraps or quotes the parser's error: url.Parse echoes the
|
||||
// whole URL, password included.
|
||||
var errInvalidRedisURL = errors.New("cooler: " + RedisURLKey + " is not a valid redis://, rediss:// or unix:// URL")
|
||||
|
||||
// OpenRedis builds a go-redis client from a redis:// (plain), rediss:// (TLS)
|
||||
// or unix:// URL, such as the value of redis.url. It does not dial: go-redis
|
||||
// connects on the first command. A URL that does not parse yields an error that
|
||||
// names redis.url and never contains the URL or its credentials.
|
||||
//
|
||||
// The client skips the CLIENT SETINFO and maintenance-notification handshakes,
|
||||
// which older servers and test doubles do not know.
|
||||
func OpenRedis(ctx context.Context, rawURL string) (*redis.Client, error) {
|
||||
_ = ctx // reserved: go-redis does not dial at construction
|
||||
rawURL = strings.TrimSpace(rawURL)
|
||||
if rawURL == "" {
|
||||
return nil, errors.New("cooler: " + RedisURLKey + " is empty")
|
||||
}
|
||||
opt, err := redis.ParseURL(rawURL)
|
||||
if err != nil {
|
||||
return nil, errInvalidRedisURL
|
||||
}
|
||||
opt.DisableIdentity = true
|
||||
opt.MaintNotificationsConfig = &maintnotifications.Config{Mode: maintnotifications.ModeDisabled}
|
||||
return redis.NewClient(opt), nil
|
||||
}
|
||||
|
||||
// redisEndpoint is the part of a client's address that may be logged: the
|
||||
// host and port (or socket path), never the URL, user or password.
|
||||
func redisEndpoint(c *redis.Client) string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return c.Options().Addr
|
||||
}
|
||||
Reference in New Issue
Block a user