Skip to content

Commit

Permalink
Adds redis sentinel support (gomods#1554)
Browse files Browse the repository at this point in the history
* Adds redis sentinel support

Fixes gomods#1553

* Fix redis-sentinel test hostnames

* Fix redis master name again

* Fix redis sentinel port in tests

* Upgrade the redis client

* Rmoeve accidental config change

* Fix default config

* Addresses review comments

* Add documentation on single flight mechanisms

* Fix spelling issues

* Fix formatting

Co-authored-by: Aaron Schlesinger <[email protected]>
  • Loading branch information
twexler and arschles authored Mar 17, 2020
1 parent 3338441 commit 939e695
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 12 deletions.
13 changes: 12 additions & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ steps:
ATHENS_MONGO_STORAGE_URL: mongodb://mongo:27017
ATHENS_MINIO_ENDPOINT: minio:9000
REDIS_TEST_ENDPOINT: redis:6379
REDIS_SENTINEL_TEST_ENDPOINT: redis-sentinel:26379
REDIS_SENTINEL_TEST_MASTER_NAME: redis-1
REDIS_SENTINEL_TEST_PASSWORD: sekret
PROTECTED_REDIS_TEST_ENDPOINT: protectedredis:6380
ATHENS_PROTECTED_REDIS_PASSWORD: AthensPass1
GCS_SERVICE_ACCOUNT:
Expand Down Expand Up @@ -156,13 +159,21 @@ services:
image: redis
ports:
- 6379
- name: redis-sentinel
image: bitnami/redis-sentinel
environment:
REDIS_MASTER_HOST: redis
REDIS_MASTER_SET: redis-1
REDIS_SENTINEL_PASSWORD: sekret
REDIS_SENTINEL_QUORUM: "1"
ports:
- 26379
- name: protectedredis
image: redis
ports:
- 6380
commands:
- "redis-server ./test/redis.conf"

- name: athens-proxy
image: gomods/athens:canary
pull: always
Expand Down
10 changes: 10 additions & 0 deletions cmd/proxy/actions/app_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ func getSingleFlight(c *config.Config, checker storage.Checker) (stash.Wrapper,
return nil, fmt.Errorf("Redis config must be present")
}
return stash.WithRedisLock(c.SingleFlight.Redis.Endpoint, c.SingleFlight.Redis.Password, checker)
case "redis-sentinel":
if c.SingleFlight == nil || c.SingleFlight.RedisSentinel == nil {
return nil, fmt.Errorf("Redis config must be present")
}
return stash.WithRedisSentinelLock(
c.SingleFlight.RedisSentinel.Endpoints,
c.SingleFlight.RedisSentinel.MasterName,
c.SingleFlight.RedisSentinel.SentinelPassword,
checker,
)
case "gcp":
if c.StorageType != "gcp" {
return nil, fmt.Errorf("gcp SingleFlight only works with a gcp storage type and not: %v", c.StorageType)
Expand Down
17 changes: 16 additions & 1 deletion config.dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ DownloadURL = ""
# and the second request will wait for the first one to finish so that
# it doesn't override the storage.

# Options are ["memory", "etcd", "redis", "gcp", "azureblob"]
# Options are ["memory", "etcd", "redis", "redis-sentinel", "gcp", "azureblob"]

# The default option is "memory" which means that only one instance of Athens
# should be used.
Expand All @@ -275,6 +275,10 @@ DownloadURL = ""
# and therefore it will use its strong-consistency features to ensure
# that only one module is ever written even when concurrent saves happen
# at the same time.
# The "redis" single flight will use a single redis instance as a locking mechanism
# for updating the underlying storage
# The "redis-sentinel" single flight works similarly to "redis" but obtains a redis connection
# via a redis-sentinel
# Env override: ATHENS_SINGLE_FLIGHT_TYPE
SingleFlightType = "memory"

Expand All @@ -290,6 +294,17 @@ SingleFlightType = "memory"
# TODO(marwan): enable multiple endpoints for redis clusters.
# Env override: ATHENS_REDIS_ENDPOINT
Endpoint = "127.0.0.1:6379"
[SingleFlight.RedisSentinel]
# Endpoints is the redis sentinel endpoints to discover a redis
# master for a SingleFlight lock.
# Env override: ATHENS_REDIS_SENTINEL_ENDPOINTS
Endpoints = ["127.0.0.1:26379"]
# MasterName is the redis sentinel master name to use to discover
# the master for a SingleFlight lock
MasterName = "redis-1"
# SentinelPassword is an optional password for authenticating with
# redis sentinel
SentinelPassword = "sekret"

# Password is the password for a redis SingleFlight lock.
# Env override: ATHENS_REDIS_PASSWORD
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ services:
image: redis
ports:
- 6379:6379
redis-sentinel:
image: bitnami/redis-sentinel
environment:
- REDIS_MASTER_HOST=redis
- REDIS_MASTER_SET=redis-1
- REDIS_SENTINEL_PASSWORD=sekret
- REDIS_SENTINEL_QUORUM=1
ports:
- 26379:26379
protectedredis:
image: redis
ports:
Expand Down
92 changes: 92 additions & 0 deletions docs/content/configuration/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,95 @@ It assumes that you already have the following:
# Name of container in the blob storage
# Env override: ATHENS_AZURE_CONTAINER_NAME
ContainerName = "MY_AZURE_BLOB_CONTAINER_NAME"

## Running multiple Athens pointed at the same storage

Athens has the ability to run concurrently pointed at the same storage medium, using
a distributed locking mechanism called "single flight".

By default, Athens is configured to use the `memory` single flight, which
stores locks in local memory. This works when running a single Athens instance, given
the process has access to it's own memory. However, when running multiple Athens instances
pointed at the same storage, a distributed locking mechansism is required.

Athens supports several distributed locking mechanisms:

- `etcd`
- `redis`
- `redis-sentinel`
- `gcp` (available when using the `gcp` storage type)
- `azureblob` (available when using the `azureblob` storage type)

Setting the `SingleFlightType` (or `ATHENS_SINGLE_FLIGHT TYPE` in the environment) configuration
value will enable usage of one of the above mechanisms. The `azureblob` and `gcp` types require
no extra configuration.

### Using etcd as the single flight mechanism

Using the `etcd` mechanism is very simple, just a comma separated list of etcd endpoints.
The recommend configuration is 3 endpoints, however, more can be used.

SingleFlightType = "etcd"

[SingleFlight]
[SingleFlight.Etcd]
# Env override: ATHENS_ETCD_ENDPOINTS
Endpoints = "localhost:2379,localhost:22379,localhost:32379"

### Using redis as the single flight mechanism

Athens supports two mechanisms of communicating with redis: direct connection, and
connecting via redis sentinels.

#### Direct connection to redis

Using a direct connection to redis is simple, and only requires a single `redis-server`.
You can also optionally specify a password to connect to the redis server with

SingleFlightType = "redis"

[SingleFlight]
[SingleFlight.Redis]
# Endpoint is the redis endpoint for the single flight mechanism
# Env override: ATHENS_REDIS_ENDPOINT
Endpoint = "127.0.0.1:6379"

# Password is the password for the redis instance
# Env override: ATHENS_REDIS_PASSWORD
Password = ""

#### Connecting to redis via redis sentinel

**NOTE**: redis-sentinel requires a working knowledge of redis and is not recommended for
everyone.

redis sentinel is a high-availability set up for redis, it provides automated monitoring, replication,
failover and configuration of multiple redis servers in a leader-follower setup. It is more
complex than running a single redis server and requires multiple disperate instances of redis
running distributed across nodes.

For more details on redis-sentinel, check out the [documentation](https://redis.io/topics/sentinel)

As redis-sentinel is a more complex set up of redis, it requires more configuration than standard redis.

Required configuration:

- `Endpoints` is a list of redis-sentinel endpoints to connect to, typically 3, but more can be used
- `MasterName` is the named master instance, as configured in the `redis-sentinel` [configuration](https://redis.io/topics/sentinel#configuring-sentinel)

Optionally, like `redis`, you can also specify a password to connect to the `redis-sentinel` endpoints with

SingleFlightType = "redis-sentinel"

[SingleFlight]
[SingleFlight.RedisSentinel]
# Endpoints is the redis sentinel endpoints to discover a redis
# master for a SingleFlight lock.
# Env override: ATHENS_REDIS_SENTINEL_ENDPOINTS
Endpoints = ["127.0.0.1:26379"]
# MasterName is the redis sentinel master name to use to discover
# the master for a SingleFlight lock
MasterName = "redis-1"
# SentinelPassword is an optional password for authenticating with
# redis sentinel
SentinelPassword = "sekret"
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ require (
github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20180917103902-e6c7f767dc57
github.com/aws/aws-sdk-go v1.15.24
github.com/bsm/redis-lock v8.0.0+incompatible
github.com/bsm/redislock v0.4.2
github.com/codegangsta/negroni v1.0.0 // indirect
github.com/fatih/color v1.7.0
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/go-redis/redis v6.15.2+incompatible
github.com/go-redis/redis/v7 v7.2.0
github.com/gobuffalo/envy v1.6.7
github.com/gobuffalo/httptest v1.0.4
github.com/golang/snappy v0.0.1 // indirect
Expand Down
21 changes: 21 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k=
github.com/bsm/redis-lock v8.0.0+incompatible h1:QgB0J2pNG8hUfndTIvpPh38F5XsUTTvO7x8Sls++9Mk=
github.com/bsm/redis-lock v8.0.0+incompatible/go.mod h1:8dGkQ5GimBCahwF2R67tqGCJbyDZSp0gzO7wq3pDrik=
github.com/bsm/redislock v0.4.2 h1:+7WydoauDwf5Qw0ajaI/g3t26dQ/ovGU0Dv59sVvQzc=
github.com/bsm/redislock v0.4.2/go.mod h1:zeuSDdDFtEDtbAgKsw7NDucfSVR0zLWBv8tMpro/6UM=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
Expand Down Expand Up @@ -90,6 +92,10 @@ github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rm
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
github.com/go-redis/redis/v7 v7.0.0-beta.4/go.mod h1:xhhSbUMTsleRPur+Vgx9sUHtyN33bdjxY+9/0n9Ig8s=
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
Expand All @@ -111,6 +117,8 @@ github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
Expand Down Expand Up @@ -209,9 +217,15 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
Expand Down Expand Up @@ -333,6 +347,8 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand All @@ -353,6 +369,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down Expand Up @@ -388,6 +406,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
Expand All @@ -403,6 +422,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
5 changes: 5 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ func defaultConfig() *Config {
SingleFlight: &SingleFlight{
Etcd: &Etcd{"localhost:2379,localhost:22379,localhost:32379"},
Redis: &Redis{"127.0.0.1:6379", ""},
RedisSentinel: &RedisSentinel{
Endpoints: []string{"127.0.0.1:26379"},
MasterName: "redis-1",
SentinelPassword: "sekret",
},
},
}
}
Expand Down
13 changes: 11 additions & 2 deletions pkg/config/singleflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ package config
// backend configurations for a distributed
// lock or single flight mechanism.
type SingleFlight struct {
Etcd *Etcd
Redis *Redis
Etcd *Etcd
Redis *Redis
RedisSentinel *RedisSentinel
}

// Etcd holds client side configuration
Expand All @@ -21,3 +22,11 @@ type Redis struct {
Endpoint string `envconfig:"ATHENS_REDIS_ENDPOINT"`
Password string `envconfig:"ATHENS_REDIS_PASSWORD"`
}

// RedisSentinel is the configuration for using redis with sentinel
// for SingleFlight
type RedisSentinel struct {
Endpoints []string `envconfig:"ATHENS_REDIS_SENTINEL_ENDPOINTS"`
MasterName string `envconfig:"ATHENS_REDIS_SENTINEL_MASTER_NAME"`
SentinelPassword string `envconfig:"ATHENS_REDIS_SENTINEL_PASSWORD"`
}
14 changes: 6 additions & 8 deletions pkg/stash/with_redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"context"
"time"

lock "github.com/bsm/redis-lock"
"github.com/go-redis/redis"
lock "github.com/bsm/redislock"
"github.com/go-redis/redis/v7"
"github.com/gomods/athens/pkg/config"
"github.com/gomods/athens/pkg/errors"
"github.com/gomods/athens/pkg/observ"
Expand Down Expand Up @@ -44,17 +44,15 @@ func (s *redisLock) Stash(ctx context.Context, mod, ver string) (newVer string,
mv := config.FmtModVer(mod, ver)

// Obtain a new lock with default settings
lock, err := lock.Obtain(s.client, mv, &lock.Options{
LockTimeout: time.Minute * 5,
RetryCount: 60 * 5,
RetryDelay: time.Second,
lock, err := lock.Obtain(s.client, mv, time.Minute*5, &lock.Options{
RetryStrategy: lock.LimitRetry(lock.LinearBackoff(time.Second), 60*5),
})
if err != nil {
return ver, errors.E(op, err)
}
defer func() {
const op errors.Op = "redis.Unlock"
lockErr := lock.Unlock()
const op errors.Op = "redis.Release"
lockErr := lock.Release()
if err == nil && lockErr != nil {
err = errors.E(op, lockErr)
}
Expand Down
30 changes: 30 additions & 0 deletions pkg/stash/with_redis_sentinel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package stash

import (
"github.com/go-redis/redis/v7"
"github.com/gomods/athens/pkg/errors"
"github.com/gomods/athens/pkg/storage"
)

// WithRedisSentinelLock returns a distributed singleflight
// with a redis cluster that utilizes sentinel for quorum and failover
func WithRedisSentinelLock(endpoints []string, master, password string, checker storage.Checker) (Wrapper, error) {
const op errors.Op = "stash.WithRedisSentinelLock"
// The redis client constructor does not return an error when no endpoints
// are provided, so we check for ourselves.
if len(endpoints) == 0 {
return nil, errors.E(op, "no endpoints specified")
}
client := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: master,
SentinelAddrs: endpoints,
SentinelPassword: password,
})
_, err := client.Ping().Result()
if err != nil {
return nil, errors.E(op, err)
}
return func(s Stasher) Stasher {
return &redisLock{client, s, checker}
}, nil
}
Loading

0 comments on commit 939e695

Please sign in to comment.