Back to Home

RedLock.NET: distributed locks in .NET

The article describes the implementation of distributed locks in .NET using RedLock.NET and alternatives via PostgreSQL. Standard primitives, Mutex, DB locks are compared. SQL examples and best practices for microservices in Kubernetes are provided.

Distributed locks in .NET: RedLock vs DB approaches
Advertisement 728x90

# 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.

Google AdInline article slot

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:

Google AdInline article slot
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:

Google AdInline article slot
  • Unique resource per 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: TimeSpan for 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

Advertisement 728x90

Read Next