Synchronizing access to shared resources across independent server nodes is a classic challenge in distributed architectures. Relying on a naive SET resource_id client_id NX PX 30000 command on a single Redis instance introduces catastrophic race conditions under network partitions, GC pauses, and master-replica failovers.
1. Why Single-Node Redis Locks Fail Under Failure
Redis master-replica replication is fundamentally asynchronous. Consider this standard failure scenario:
- Client 1 acquires a lock on the Redis Master node.
- The Master node crashes before the write replication packet reaches the Replica node.
- Sentinel or Cluster promotes the Replica to the new Master.
- Client 2 requests the same lock. The new Master grants it because the key is missing.
- Catastrophe: Both Client 1 and Client 2 believe they hold the exclusive lock simultaneously, leading to silent database state corruption.
2. Martin Kleppmann's Critique & Fencing Tokens
Even with consensus-backed lock managers, a client can experience an unexpected pause (e.g. Stop-The-World GC or CPU throttling) immediately after acquiring a lock. By the time the client resumes, its lock lease has expired and been acquired by another node.
Every granted lock must return a strictly monotonically increasing fencing number. When writing to the storage tier, the storage engine rejects any update possessing a token lower than the highest token observed so far.
package main
import (
"context"
"fmt"
"log"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/concurrency"
)
func RunWithEtcdLock() {
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379"},
})
if err != nil {
log.Fatal(err)
}
defer cli.Close()
session, err := concurrency.NewSession(cli, concurrency.WithTTL(10))
if err != nil {
log.Fatal(err)
}
defer session.Close()
mutex := concurrency.NewMutex(session, "/locks/order-settlement/1001")
ctx := context.Background()
if err := mutex.Lock(ctx); err != nil {
log.Fatalf("Failed to acquire lock: %v", err)
}
fmt.Println("Consensus-backed lock acquired safely via Raft!")
// Execute critical section...
if err := mutex.Unlock(ctx); err != nil {
log.Fatal(err)
}
}