跪拜 Guibai
← All articles
Backend · Redis

Five Redis Distributed Lock Traps and the Go Code That Survives Them

By 左诗右码 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Redis is the default distributed lock for most teams, but the gap between a `SETNX` snippet and a lock that survives timeouts, goroutine leaks, and failovers is wide enough to cause data corruption. The five patterns here—especially the fencing token—are the difference between a lock that mostly works and one that actually protects shared state.

Summary

Building a Redis distributed lock in Go starts with an atomic `SET key value NX EX time` and a unique token to prevent one goroutine from deleting another's lock. The unlock path must run a Lua script that checks the token and deletes in a single atomic step. A watchdog goroutine, driven by a ticker and tied to a `context.Context`, renews the lock's TTL so a slow operation doesn't lose the lock mid-flight.

Go's missing goroutine ID forces a different reentrancy design: pass the lock token through the call chain or stash it in `context.Context`. At the Redis level, a hash structure keyed by a UUID-plus-trace-ID field tracks reentry counts. The deepest trap is master-slave failover—an async replication gap can hand the same lock to two callers. Redlock doesn't fix it because it trusts system clocks that can jump.

For strong consistency, the answer is to leave Redis behind for etcd or ZooKeeper and to adopt fencing tokens. Every write carries a monotonically increasing token; downstream systems reject stale tokens, making the lock's expiry irrelevant to correctness.

Takeaways
An atomic `SET key value NX EX time` replaces the broken two-step `SETNX` + `Expire` sequence.
Every lock must carry a unique token; the unlock Lua script checks the token and deletes atomically to avoid deleting another caller's lock.
A watchdog goroutine renews the lock TTL at one-third the expiration interval and exits cleanly via `context.Context` cancellation or a done channel.
Go lacks a goroutine ID, so reentrant locks require passing a token or a context value through the call chain, or using a Redis hash keyed by a UUID-plus-trace-ID.
Master-slave failover loses un-replicated locks; Redlock's clock-synchronization dependency makes it unsafe under clock jumps or GC pauses.
Fencing tokens—monotonically increasing IDs checked by downstream systems—are the final defense when lock expiry cannot be trusted.
For routine workloads, `go-redsync/redsync` is sufficient; for financial correctness, switch to etcd and add database-level optimistic locking.
Conclusions

Go's deliberate hiding of goroutine IDs is not an obstacle to work around with assembly hacks—it forces a design where identity travels with the request context, which is cleaner and more testable.

The watchdog pattern is only as safe as its cancellation wiring; a leaked goroutine that keeps renewing a lock after the caller has moved on is a silent correctness bug.

Martin Kleppmann's critique of Redlock is a reminder that distributed correctness cannot be bolted onto an eventually-consistent data store with client-side clocks—the model itself must provide linearizability.

Fencing tokens invert the trust model: instead of trying to make the lock perfect, you make the resource itself reject stale operations, which is a cheaper and more reliable guarantee.

Concepts & terms
Fencing Token
A monotonically increasing number issued alongside a distributed lock. Every write to a shared resource includes the token; the resource rejects any write whose token is lower than the highest one it has already seen, preventing a delayed or expired lock holder from corrupting state.
Watchdog (Lock Renewal)
A background goroutine that periodically extends a lock's TTL while the holder is still active. It prevents a lock from expiring during a long-running operation, but must be tied to a cancellable context to avoid leaking goroutines.
Redlock
A multi-node Redis locking algorithm that acquires locks from a majority of independent instances. It is vulnerable to clock jumps and process pauses because its safety depends on synchronized, accurate system clocks across all nodes.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗