Skip to content

Commit 351867b

Browse files
authored
feat: redlock example (#10)
* feat: redlock with go example * feat: redlock with go example x02
1 parent 7ac600b commit 351867b

File tree

6 files changed

+209
-0
lines changed

6 files changed

+209
-0
lines changed

redlock-go/Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM golang:1.20 as builder
2+
3+
WORKDIR /app
4+
5+
COPY go.mod go.sum ./
6+
RUN go mod download
7+
8+
COPY app/ ./app/
9+
10+
RUN CGO_ENABLED=0 GOOS=linux go build -v -o /go/bin/app ./app/main.go
11+
12+
FROM alpine:latest
13+
14+
COPY --from=builder /go/bin/app /go/bin/app
15+
16+
CMD ["/go/bin/app"]

redlock-go/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Example: Redlock with Go
2+
3+
[Redlock](https://redis.io/docs/manual/patterns/distributed-locks/) is a recognized algorithm based on Redis for
4+
distributed locks, ensuring consistent operation and protection against failures such as network partitions and Redis crashes.
5+
It operates by having a client application send lock requests, using [`SET`](https://www.dragonflydb.io/docs/command-reference/strings/set.md) commands, to multiple **primary Redis instances**.
6+
The lock is successfully acquired when more than half of these instances agree on the lock acquisition.
7+
To release the lock, the client issues [`DEL`](https://www.dragonflydb.io/docs/command-reference/generic/del) commands to all the instances involved.
8+
Redlock also takes into account the lock validity time, retry on failure, lock extension, and many other aspects, which makes it a robust and reliable solution for distributed locking.
9+
10+
Since Dragonfly is highly compatible with Redis and both `SET` and `DEL` commands are fully supported, Redlock implementations can be easily used with Dragonfly.
11+
12+
## Packages Used
13+
14+
- The [go-redis](https://github.com/redis/go-redis) client is used. It has strongly typed methods for various commands.
15+
It is also the recommended Go client to interact with Dragonfly.
16+
- The [redsync](https://github.com/go-redsync/redsync) is a Go implementation of the Redlock algorithm.
17+
It is used to acquire and release locks in a distributed environment.
18+
19+
## Local Setup
20+
21+
- Make sure that you have [Go v1.20+](https://go.dev/dl/) installed locally.
22+
- Make sure that you have [Docker](https://docs.docker.com/engine/install/) installed locally.
23+
- Run Dragonfly instances & the Redlock service:
24+
25+
```bash
26+
# within the root directory of this example (dragonfly-examples/redlock-go)
27+
docker compose build --no-cache && docker compose up
28+
```
29+
30+
- Use the API endpoint to acquire the lock:
31+
32+
```bash
33+
curl -v --url http://localhost:8080/try-to-acquire-global-lock
34+
```
35+
36+
- For the first time, the lock should be acquired successfully:
37+
38+
```text
39+
HTTP/1.1 200 OK
40+
Date: Thu, 15 Feb 2024 19:29:09 GMT
41+
Content-Length: 13
42+
Content-Type: text/plain; charset=utf-8
43+
44+
lock acquired
45+
```
46+
47+
- If we try to acquire the lock again in a short period, it should fail:
48+
49+
```text
50+
HTTP/1.1 400 Bad Request
51+
Content-Type: text/plain; charset=utf-8
52+
X-Content-Type-Options: nosniff
53+
Date: Thu, 15 Feb 2024 19:29:56 GMT
54+
Content-Length: 42
55+
56+
lock already taken, locked nodes: [0 1 2]
57+
```

redlock-go/app/main.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
"time"
6+
7+
"github.com/go-redsync/redsync/v4"
8+
redsyncredis "github.com/go-redsync/redsync/v4/redis"
9+
redsyncpool "github.com/go-redsync/redsync/v4/redis/goredis/v9"
10+
"github.com/redis/go-redis/v9"
11+
)
12+
13+
// These hosts are reachable from within the Docker network.
14+
const (
15+
dragonflyHost0 = "dragonfly-instance-0:6379"
16+
dragonflyHost1 = "dragonfly-instance-1:6379"
17+
dragonflyHost2 = "dragonfly-instance-2:6379"
18+
)
19+
20+
const (
21+
// The name of the global lock.
22+
globalLockKeyName = "my-global-lock"
23+
24+
// The expiry of the global lock.
25+
globalLockExpiry = time.Minute
26+
27+
// Number of retries to acquire the global lock.
28+
globalLockRetries = 8
29+
30+
// The delay between retries to acquire the global lock.
31+
globalLockRetryDelay = 10 * time.Millisecond
32+
)
33+
34+
func main() {
35+
// Create three clients for each instance of Dragonfly.
36+
var (
37+
hosts = []string{dragonflyHost0, dragonflyHost1, dragonflyHost2}
38+
clients = make([]redsyncredis.Pool, len(hosts))
39+
)
40+
for idx, addr := range hosts {
41+
client := redis.NewClient(&redis.Options{
42+
Addr: addr,
43+
})
44+
clients[idx] = redsyncpool.NewPool(client)
45+
}
46+
47+
// Create an instance of 'Redsync' to work with locks.
48+
rs := redsync.New(clients...)
49+
50+
// Create a global lock mutex.
51+
globalMutex := rs.NewMutex(
52+
globalLockKeyName,
53+
redsync.WithExpiry(globalLockExpiry),
54+
redsync.WithTries(globalLockRetries),
55+
redsync.WithRetryDelay(globalLockRetryDelay),
56+
)
57+
58+
// Create an HTTP server that exposes an endpoint to acquire the global lock.
59+
// Normally, the lock should be released after the work is done.
60+
// For demonstration purposes, the lock is released after the configured expiry,
61+
// so that you can check the lock keys in Dragonfly instances.
62+
http.HandleFunc("/try-to-acquire-global-lock", func(w http.ResponseWriter, r *http.Request) {
63+
if err := globalMutex.Lock(); err != nil {
64+
http.Error(w, err.Error(), http.StatusBadRequest)
65+
return
66+
}
67+
w.WriteHeader(http.StatusOK)
68+
_, _ = w.Write([]byte("lock acquired"))
69+
})
70+
71+
if err := http.ListenAndServe(":8080", nil); err != nil {
72+
panic(err)
73+
}
74+
}

redlock-go/docker-compose.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
version: '3'
2+
services:
3+
dragonfly-instance-0:
4+
container_name: "dragonfly-instance-0"
5+
image: 'ghcr.io/dragonflydb/dragonfly:v1.14.3-ubuntu'
6+
ulimits:
7+
memlock: -1
8+
ports:
9+
- "6379:6379"
10+
dragonfly-instance-1:
11+
container_name: "dragonfly-instance-1"
12+
image: 'ghcr.io/dragonflydb/dragonfly:v1.14.3-ubuntu'
13+
ulimits:
14+
memlock: -1
15+
ports:
16+
- "6380:6379"
17+
dragonfly-instance-2:
18+
container_name: "dragonfly-instance-2"
19+
image: 'ghcr.io/dragonflydb/dragonfly:v1.14.3-ubuntu'
20+
ulimits:
21+
memlock: -1
22+
ports:
23+
- "6381:6379"
24+
redlock-service:
25+
container_name: "redlock-service"
26+
build:
27+
context: .
28+
dockerfile: Dockerfile
29+
depends_on:
30+
- dragonfly-instance-0
31+
- dragonfly-instance-1
32+
- dragonfly-instance-2
33+
ports:
34+
- "8080:8080"

redlock-go/go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module example.com/m/v2
2+
3+
go 1.20
4+
5+
require github.com/redis/go-redis/v9 v9.4.0
6+
7+
require (
8+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
9+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
10+
github.com/go-redsync/redsync/v4 v4.11.0 // indirect
11+
github.com/hashicorp/errwrap v1.1.0 // indirect
12+
github.com/hashicorp/go-multierror v1.1.1 // indirect
13+
)

redlock-go/go.sum

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
2+
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
3+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
4+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
6+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
7+
github.com/go-redsync/redsync/v4 v4.11.0 h1:OPEcAxHBb95EzfwCKWM93ksOwHd5bTce2BD4+R14N6k=
8+
github.com/go-redsync/redsync/v4 v4.11.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc=
9+
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
10+
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
11+
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
12+
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
13+
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
14+
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
15+
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=

0 commit comments

Comments
 (0)