SoFunction
Updated on 2025-03-03

Common ways to implement distributed locks using Redis

Methods to implement distributed locks

Use Redis's SET command

Redis'sSETThe command supports setting key-value pairs and can be passedNXandEXParameters to implement atomic operations, thereby realizing distributed locking.

  • NX: The key is set only when the key does not exist.
  • EX: Set the expiration time (seconds) of the key.

Sample code

Here is a sample code that implements distributed locks using Go and Redis:

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"/go-redis/redis/v8"
)

var ctx = ()

func main() {
	// Initialize the Redis client	rdb := (&{
		Addr:     "localhost:6379", // Redis Address		Password: "",               // password		DB:       0,                // Database number	})

	// The key name and timeout of the lock	key := "my_lock"
	timeout :=  * 10

	// Try to acquire the lock	lockAcquired := acquireLock(ctx, rdb, key, timeout)
	if lockAcquired {
		defer releaseLock(ctx, rdb, key)

		// Perform the locking operation here		("Lock acquired, performing critical section operations...")
		( * 5) // Simulation time-consuming operation		("Critical section operations completed.")
	} else {
		("Failed to acquire lock.")
	}
}

// acquireLock try to acquire the lockfunc acquireLock(ctx , client *, key string, timeout ) bool {
	// Set key-value pairs, set only when the key does not exist, and set the expiration time	result, err := (ctx, key, "locked", timeout).Result()
	if err != nil {
		("Failed to acquire lock: %v", err)
	}
	return result
}

// releaseLock releases the lockfunc releaseLock(ctx , client *, key string) {
	// Delete key	err := (ctx, key).Err()
	if err != nil {
		("Failed to release lock: %v", err)
	}
}

Things to note

  1. Timeout: Set a reasonable timeout to prevent deadlock. If the process holding the lock crashes, the lock will not be occupied forever.
  2. Idepotency: Ensure that the operation of releasing the lock is idempotent, that is, multiple callsreleaseLockNo problem.
  3. Competition conditions: In high concurrency scenarios, competition conditions may occur. Atomic operations can be ensured through Lua scripts.
  4. Security: Make sure that only processes holding the lock can release the lock. Can be passed inSETSet unique values ​​in the command to achieve this.

Ensure atomicity using Lua scripts

To ensure that the operation of releasing the lock is atomic, it can be implemented using a Lua script. Here is an improved example:

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"/go-redis/redis/v8"
)

var ctx = ()

func main() {
	// Initialize the Redis client	rdb := (&{
		Addr:     "localhost:6379", // Redis Address		Password: "",               // password		DB:       0,                // Database number	})

	// The key name and timeout of the lock	key := "my_lock"
	value := "unique_value"
	timeout :=  * 10

	// Try to acquire the lock	lockAcquired := acquireLock(ctx, rdb, key, value, timeout)
	if lockAcquired {
		defer releaseLock(ctx, rdb, key, value)

		// Perform the locking operation here		("Lock acquired, performing critical section operations...")
		( * 5) // Simulation time-consuming operation		("Critical section operations completed.")
	} else {
		("Failed to acquire lock.")
	}
}

// acquireLock try to acquire the lockfunc acquireLock(ctx , client *, key, value string, timeout ) bool {
	// Set key-value pairs, set only when the key does not exist, and set the expiration time	result, err := (ctx, key, value, timeout).Result()
	if err != nil {
		("Failed to acquire lock: %v", err)
	}
	return result
}

// releaseLock releases the lockfunc releaseLock(ctx , client *, key, value string) {
	// Use Lua script to ensure that the operation to release the lock is atomic	script := (`
		if ("get", KEYS[1]) == ARGV[1] then
			return ("del", KEYS[1])
		else
			return 0
		end
	`)
	err := (ctx, client, []string{key}, value).Err()
	if err != nil {
		("Failed to release lock: %v", err)
	}
}

useSETCommands and Lua scripts ensure the atomicity and security of operations.

====================

In the implementation of distributed locks,keyis a very important parameter, which is used to uniquely identify a lock. The following detailed explanationkeyexistacquireLockFunctions of the method:

The role of key

  1. Unique ID lock

    • keyis a string that uniquely identifies a specific lock. Different locks should have differentkey, this ensures that different resources can be locked independently.
    • For example, if you have two resourcesresource1andresource2, you can set different for them separatelykey,for example"lock:resource1"and"lock:resource2"
  2. The status of the storage lock

    • When you try to acquire the lock,keyUsed as a key in Redis. If this key already exists, it means that other clients have already held the lock.
    • If the key does not exist, Redis will set this key and set its value to the value you provide (for example"locked"or a unique identifier).
  3. Set expiration time

    • While setting the key, you can set an expiration time for the key (usingEXParameters). This prevents the lock from being permanently occupied due to client crashes or other reasons.
    • The expiration time ensures that even if there is a problem with the client holding the lock, the lock will eventually be automatically released.

Key usage in the sample code

In the previous example code,keyUsed foracquireLockMethod:

func acquireLock(ctx , client *, key, value string, timeout ) bool {
    // Set key-value pairs, set only when the key does not exist, and set the expiration time    result, err := (ctx, key, value, timeout).Result()
    if err != nil {
        ("Failed to acquire lock: %v", err)
    }
    return result
}
  • key: The key used to uniquely identify the lock.
  • value: Set the value of the key, which can be a fixed string (such as "locked") or a unique identifier (such as the unique ID of the client).
  • timeout: Set the expiration time of the key, in seconds.

Specific examples

Suppose you have two resources resource1 and resource2, you can set different keys for them respectively:

key1 := "lock:resource1"
key2 := "lock:resource2"

// Try to get the lock of resource1lockAcquired1 := acquireLock(ctx, rdb, key1, "unique_value1",  * 10)
if lockAcquired1 {
    defer releaseLock(ctx, rdb, key1, "unique_value1")

    // Perform the locking operation here    ("Lock acquired for resource1, performing critical section operations...")
    ( * 5) // Simulation time-consuming operation    ("Critical section operations completed for resource1.")
} else {
    ("Failed to acquire lock for resource1.")
}

// Try to get the lock of resource2lockAcquired2 := acquireLock(ctx, rdb, key2, "unique_value2",  * 10)
if lockAcquired2 {
    defer releaseLock(ctx, rdb, key2, "unique_value2")

    // Perform the locking operation here    ("Lock acquired for resource2, performing critical section operations...")
    ( * 5) // Simulation time-consuming operation    ("Critical section operations completed for resource2.")
} else {
    ("Failed to acquire lock for resource2.")
}

KEY

keyIt plays a role in uniquely identifying the lock in the implementation of distributed locks. By setting different resourceskey, it can ensure that different resources can be locked independently. at the same time,keyIt is also used to store the state of the lock and can set the expiration time to prevent deadlocks.

This is the article about the common methods of Go using Redis to implement distributed locks. For more information about Go Redis distributed locks, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!