# Implementing Distributed Locks in .NET with RedLock.NET
Distributed locks provide exclusive access to critical sections in systems with multiple service instances. This is crucial for microservices in Kubernetes, where pods or different services compete for resources. The primary goal is atomic execution of operations, such as updating key data in the DB or calling external APIs.
Typical scenarios:
- Changing business-critical values in the database.
- Sending requests to regulatory systems (e.g., analogs of Chestny Znak).
- Synchronization between pods of the same microservice or services in orchestration/choreography.
Standard .NET primitives like SemaphoreSlim, AsyncLock or lock aren't suitable due to the physical separation of instances. Mutex works on Windows but is problematic in async code and Docker containers.
Overview of RedLock.NET
RedLock.NET is a library for implementing the RedLock algorithm in .NET. The latest stable version is 2.3.2 (2022). The package has passed security checks with no known vulnerabilities. The author actively maintains issues on GitHub.
The RedLock algorithm uses a Redis cluster for distributed locks with TTL, ensuring safety even during network failures. The library encapsulates the logic for creating, extending, and releasing locks.
Alternatives: Locks via Database
Locks Table in PostgreSQL
The classic approach is a dedicated table for locks:
CREATE TABLE locks (
resource VARCHAR(100) PRIMARY KEY,
locked_by VARCHAR(100),
locked_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ
);
Atomic INSERT with conflict handling:
INSERT INTO locks (resource, locked_by, expires_at)
VALUES (@resource, @locked_by, NOW() + (@timespan || ' seconds')::interval)
ON CONFLICT (resource) DO UPDATE
SET locked_by = EXCLUDED.locked_by,
expires_at = EXCLUDED.expires_at
WHERE locks.expires_at < NOW()
RETURNING locked_by;
Logic:
- If the lock is absent, a new one is created with TTL.
- If the lock has expired, it's updated.
- If active, an empty result is returned.
Requirements:
- Unique
resourceper operation. - Unique
locked_by(GUID or instance ID + thread ID).
Isolation Levels
SERIALIZABLE ensures atomicity without extra tables but reduces throughput. In enterprise environments, DBAs rarely approve it due to the load. UPSERT approaches with READ COMMITTED are preferred.
Comparison of Approaches
| Approach | Pros | Cons |
|--------|-------|--------|
| .NET primitives | Simplicity, low latency | Single instance only |
| Mutex | OS-level sync | Not for Docker/Linux, async issues |
| DB locks | DB integration | DB overhead, scalability issues |
| RedLock.NET | High availability, TTL | Redis dependency |
DB locks work well for simple cases with low frequency. RedLock.NET is ideal for high-load scenarios with strong guarantees.
Practical Implementation of RedLock.NET
Installation: dotnet add package RedLock.net. Create a RedLockFactory with Redis endpoints:
var multiplexer = new ConnectionMultiplexer(...);
var factory = RedLockFactory.Create(...);
using var @lock = await factory.CreateLockAsync(resource, expiry);
if (@lock.IsAcquired) {
// critical section
}
Key parameters:
resource: string identifier (Redis key).expiry:TimeSpanfor TTL.- Quorum: majority of Redis nodes for consensus.
Failure handling: the library automatically extends locks for long-running operations via IExtensibleLock.
Scaling and Best Practices
- Use Redis Sentinel or Cluster for HA.
- Clock drift < 10ms between nodes.
- Validity time > 2x clock drift + operation time.
- Monitor lock acquisition failure rate.
In production, combine with circuit breakers for external calls in critical sections.
Key Takeaways
- RedLock.NET provides quorum-based safety without a single point of failure.
- DB locks are simpler but vulnerable to DB overload and don't scale.
- Always set TTL to protect against zombie locks.
- Test under load with network partition simulations.
- For .NET Core and later, avoid Mutex in containers.
— Editorial Team
No comments yet.