Redlock

Introduction

Redlock is a recognized algorithm based on Redis for distributed locks, ensuring consistent operation and protection against failures such as network partitions and Redis crashes. It operates by having a client application send lock requests, using SET commands, to multiple primary Redis instances. The lock is successfully acquired when more than half of these instances agree on the lock acquisition. To release the lock, the client application uses a Lua script, which involves the GET command and the DEL command, to perform compare-and-delete operations on all the instances involved. 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.

Since Dragonfly is highly compatible with Redis, Redlock implementations can be easily used with Dragonfly.

Implementations

The following is a list of Redlock implementations in various languages, ordered by the language used:

Using Redlock with Dragonfly (Go)

Let’s look at how to use Redlock with Dragonfly in a Go application using the redsync library. Imagine that you have three Dragonfly instances, which are independent primary instances capable of handling lock requests. This setup can be described using a docker-compose.yml file as follows:

  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"

Then, you can use the redsync library to acquire and release locks in a Go application:

  1. package main
  2. import (
  3. "net/http"
  4. "time"
  5. "github.com/go-redsync/redsync/v4"
  6. redsyncredis "github.com/go-redsync/redsync/v4/redis"
  7. redsyncpool "github.com/go-redsync/redsync/v4/redis/goredis/v9"
  8. "github.com/redis/go-redis/v9"
  9. )
  10. // These hosts are reachable from within the Docker network.
  11. const (
  12. dragonflyHost0 = "dragonfly-instance-0:6379"
  13. dragonflyHost1 = "dragonfly-instance-1:6379"
  14. dragonflyHost2 = "dragonfly-instance-2:6379"
  15. )
  16. const (
  17. // The name of the global lock.
  18. globalLockKeyName = "my-global-lock"
  19. // The expiry of the global lock.
  20. globalLockExpiry = time.Minute
  21. // Number of retries to acquire the global lock.
  22. globalLockRetries = 8
  23. // The delay between retries to acquire the global lock.
  24. globalLockRetryDelay = 10 * time.Millisecond
  25. )
  26. func main() {
  27. // Create three clients for each instance of Dragonfly.
  28. var (
  29. hosts = []string{dragonflyHost0, dragonflyHost1, dragonflyHost2}
  30. clients = make([]redsyncredis.Pool, len(hosts))
  31. )
  32. for idx, addr := range hosts {
  33. client := redis.NewClient(&redis.Options{
  34. Addr: addr,
  35. })
  36. clients[idx] = redsyncpool.NewPool(client)
  37. }
  38. // Create an instance of 'Redsync' to work with locks.
  39. rs := redsync.New(clients...)
  40. // Create a global lock mutex.
  41. globalMutex := rs.NewMutex(
  42. globalLockKeyName,
  43. redsync.WithExpiry(globalLockExpiry),
  44. redsync.WithTries(globalLockRetries),
  45. redsync.WithRetryDelay(globalLockRetryDelay),
  46. )
  47. // Obtain a lock for the global lock mutex.
  48. // After this is successful, no one else can obtain the same lock (with the same mutex name),
  49. // until we unlock it, or it expires automatically after the specified expiry time.
  50. if err := globalMutex.Lock(); err != nil {
  51. panic(err)
  52. }
  53. // Do your work that requires the lock.
  54. // ...
  55. // Release the lock so other processes or threads can obtain a lock.
  56. if ok, err := globalMutex.Unlock(); !ok || err != nil {
  57. panic("unlock failed")
  58. }
  59. }

Useful Resources