mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-09-02 20:28:50 +02:00
feat: Allow cache reading from replicas
This commit is contained in:
@@ -444,7 +444,11 @@ make run
|
|||||||
- RedisCacheHost
|
- RedisCacheHost
|
||||||
- string
|
- string
|
||||||
- default: "redis:6379"
|
- default: "redis:6379"
|
||||||
- hostname and port for the Redis service
|
- hostname and port for the Redis write host (primary)
|
||||||
|
- RedisCacheReadHosts
|
||||||
|
- []string
|
||||||
|
- default: []
|
||||||
|
- List of Redis replica hostnames (host:port) to use for read operations. Reads are distributed round-robin across replicas. Falls back to RedisCacheHost when empty.
|
||||||
- RedisCachePassword
|
- RedisCachePassword
|
||||||
- string
|
- string
|
||||||
- default: ""
|
- default: ""
|
||||||
@@ -640,7 +644,10 @@ http:
|
|||||||
forwardedHeadersCustomName: X-Custom-Header
|
forwardedHeadersCustomName: X-Custom-Header
|
||||||
remediationHeadersCustomName: cs-remediation
|
remediationHeadersCustomName: cs-remediation
|
||||||
redisCacheEnabled: false
|
redisCacheEnabled: false
|
||||||
redisCacheHost: "redis:6379"
|
redisCacheHost: "redis-primary:6379"
|
||||||
|
redisCacheReadHosts:
|
||||||
|
- "redis-replica-1:6379"
|
||||||
|
- "redis-replica-2:6379"
|
||||||
redisCachePassword: password
|
redisCachePassword: password
|
||||||
redisCacheDatabase: "5"
|
redisCacheDatabase: "5"
|
||||||
redisCacheUnreachableBlock: true
|
redisCacheUnreachableBlock: true
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ func New(_ context.Context, next http.Handler, config *configuration.Config, nam
|
|||||||
log,
|
log,
|
||||||
config.RedisCacheEnabled,
|
config.RedisCacheEnabled,
|
||||||
config.RedisCacheHost,
|
config.RedisCacheHost,
|
||||||
|
config.RedisCacheReadHosts,
|
||||||
config.RedisCachePassword,
|
config.RedisCachePassword,
|
||||||
config.RedisCacheDatabase,
|
config.RedisCacheDatabase,
|
||||||
)
|
)
|
||||||
|
|||||||
Vendored
+43
-19
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
ttl_map "github.com/leprosus/golang-ttl-map"
|
ttl_map "github.com/leprosus/golang-ttl-map"
|
||||||
simpleredis "github.com/maxlerebourg/simpleredis"
|
simpleredis "github.com/maxlerebourg/simpleredis"
|
||||||
@@ -27,10 +28,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gochecknoglobals
|
//nolint:gochecknoglobals
|
||||||
var (
|
var cache = ttl_map.New()
|
||||||
redis simpleredis.SimpleRedis
|
|
||||||
cache = ttl_map.New()
|
|
||||||
)
|
|
||||||
|
|
||||||
type localCache struct{}
|
type localCache struct{}
|
||||||
|
|
||||||
@@ -53,32 +51,52 @@ func (localCache) delete(key string) {
|
|||||||
|
|
||||||
type redisCache struct {
|
type redisCache struct {
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
|
writer simpleredis.SimpleRedis
|
||||||
|
readers []simpleredis.SimpleRedis
|
||||||
|
counter atomic.Uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (redisCache) get(key string) (string, error) {
|
func (rc *redisCache) nextReader() *simpleredis.SimpleRedis {
|
||||||
value, err := redis.Get(key)
|
n := len(rc.readers)
|
||||||
|
if n == 0 {
|
||||||
|
return &rc.writer
|
||||||
|
}
|
||||||
|
idx := rc.counter.Add(1) % uint64(n)
|
||||||
|
return &rc.readers[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func redisResultToString(value []byte, err error) (string, error) {
|
||||||
|
if err == nil {
|
||||||
valueString := string(value)
|
valueString := string(value)
|
||||||
if err == nil && len(valueString) > 0 {
|
if len(valueString) > 0 {
|
||||||
return valueString, nil
|
return valueString, nil
|
||||||
}
|
}
|
||||||
errRedisMessage := err.Error()
|
|
||||||
if errRedisMessage == simpleredis.RedisMiss {
|
|
||||||
return "", errors.New(CacheMiss)
|
|
||||||
}
|
}
|
||||||
if errRedisMessage == simpleredis.RedisUnreachable {
|
if err != nil {
|
||||||
|
switch err.Error() {
|
||||||
|
case simpleredis.RedisMiss:
|
||||||
|
return "", errors.New(CacheMiss)
|
||||||
|
case simpleredis.RedisUnreachable:
|
||||||
return "", errors.New(CacheUnreachable)
|
return "", errors.New(CacheUnreachable)
|
||||||
}
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
return "", errors.New(CacheMiss)
|
||||||
|
}
|
||||||
|
|
||||||
func (rc redisCache) set(key, value string, duration int64) {
|
func (rc *redisCache) get(key string) (string, error) {
|
||||||
if err := redis.Set(key, []byte(value), duration); err != nil {
|
value, err := rc.nextReader().Get(key)
|
||||||
|
return redisResultToString(value, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *redisCache) set(key, value string, duration int64) {
|
||||||
|
if err := rc.writer.Set(key, []byte(value), duration); err != nil {
|
||||||
rc.log.Error("cache:setDecisionRedisCache" + err.Error())
|
rc.log.Error("cache:setDecisionRedisCache" + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc redisCache) delete(key string) {
|
func (rc *redisCache) delete(key string) {
|
||||||
if err := redis.Del(key); err != nil {
|
if err := rc.writer.Del(key); err != nil {
|
||||||
rc.log.Error("cache:deleteDecisionRedisCache " + err.Error())
|
rc.log.Error("cache:deleteDecisionRedisCache " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,15 +114,21 @@ type Client struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New Initialize cache client.
|
// New Initialize cache client.
|
||||||
func (c *Client) New(log *slog.Logger, isRedis bool, host, pass, database string) {
|
func (c *Client) New(log *slog.Logger, isRedis bool, writeHost string, readHosts []string, pass, database string) {
|
||||||
c.log = log
|
c.log = log
|
||||||
if isRedis {
|
if isRedis {
|
||||||
redis.Init(host, pass, database)
|
rc := &redisCache{log: log}
|
||||||
c.cache = &redisCache{log: log}
|
rc.writer.Init(writeHost, pass, database)
|
||||||
|
for _, h := range readHosts {
|
||||||
|
var r simpleredis.SimpleRedis
|
||||||
|
r.Init(h, pass, database)
|
||||||
|
rc.readers = append(rc.readers, r)
|
||||||
|
}
|
||||||
|
c.cache = rc
|
||||||
} else {
|
} else {
|
||||||
c.cache = &localCache{}
|
c.cache = &localCache{}
|
||||||
}
|
}
|
||||||
c.log.Debug(fmt.Sprintf("cache:New initialized isRedis:%v", isRedis))
|
c.log.Debug(fmt.Sprintf("cache:New initialized isRedis:%v writeHost:%v readHosts:%v", isRedis, writeHost, readHosts))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete delete decision in cache.
|
// Delete delete decision in cache.
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ type Config struct {
|
|||||||
ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"`
|
ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"`
|
||||||
RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"`
|
RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"`
|
||||||
RedisCacheHost string `json:"redisCacheHost,omitempty"`
|
RedisCacheHost string `json:"redisCacheHost,omitempty"`
|
||||||
|
RedisCacheReadHosts []string `json:"redisCacheReadHosts,omitempty"`
|
||||||
RedisCachePassword string `json:"redisCachePassword,omitempty"`
|
RedisCachePassword string `json:"redisCachePassword,omitempty"`
|
||||||
RedisCachePasswordFile string `json:"redisCachePasswordFile,omitempty"`
|
RedisCachePasswordFile string `json:"redisCachePasswordFile,omitempty"`
|
||||||
RedisCacheDatabase string `json:"redisCacheDatabase,omitempty"`
|
RedisCacheDatabase string `json:"redisCacheDatabase,omitempty"`
|
||||||
@@ -172,6 +173,7 @@ func New() *Config {
|
|||||||
ClientTrustedIPs: []string{},
|
ClientTrustedIPs: []string{},
|
||||||
RedisCacheEnabled: false,
|
RedisCacheEnabled: false,
|
||||||
RedisCacheHost: "redis:6379",
|
RedisCacheHost: "redis:6379",
|
||||||
|
RedisCacheReadHosts: []string{},
|
||||||
RedisCachePassword: "",
|
RedisCachePassword: "",
|
||||||
RedisCacheDatabase: "",
|
RedisCacheDatabase: "",
|
||||||
RedisCacheUnreachableBlock: true,
|
RedisCacheUnreachableBlock: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user