Powrót do strony głównej

RedLock.NET: rozproszone blokady w .NET

Artykuł opisuje implementację rozproszonych blokad w .NET z użyciem RedLock.NET i alternatyw poprzez PostgreSQL. Porównywane są standardowe prymitywy, Mutex, DB locks. Podane przykłady SQL i best practices dla mikrousług w Kubernetes.

Rozproszone locks w .NET: RedLock vs podejścia DB
Advertisement 728x90

Implementacja rozproszonych blokad w .NET z RedLock.NET

Rozproszone blokady zapewniają ekskluzywny dostęp do krytycznych sekcji w systemach z wieloma instancjami usług. Jest to istotne dla mikrousług w Kubernetes, gdzie pody lub różne usługi konkurują o zasoby. Głównym celem jest atomowe wykonywanie operacji, takich jak aktualizacja kluczowych danych w bazie danych lub wywołanie zewnętrznych API.

Typowe scenariusze:

  • Zmiana wartości krytycznych dla biznesu w bazie danych.
  • Wysyłanie zapytań do systemów regulacyjnych (np. odpowiedniki Czesnego Znaku).
  • Synchronizacja między podami jednego mikrousługi lub usługami w orkiestracji/choreografii.

Standardowe prymitywy .NET, takie jak SemaphoreSlim, AsyncLock czy lock, nie nadają się ze względu na fizyczną rozdzieloność instancji. Mutex działa na Windows, ale jest problematyczny w kodzie asynchronicznym i kontenerach Docker.

Google AdInline article slot

Przegląd RedLock.NET

RedLock.NET to biblioteka do implementacji algorytmu RedLock w .NET. Ostatnia stabilna wersja 2.3.2 (2022). Pakiet przeszedł weryfikacje bezpieczeństwa, bez znanych podatności. Autor aktywnie wspiera zgłoszenia na GitHub.

Algorytm RedLock wykorzystuje klaster Redis do rozproszonych blokad z TTL, zapewniając bezpieczeństwo nawet przy awariach sieci. Biblioteka enkapsuluje logikę: tworzenie, przedłużanie i usuwanie blokad.

Alternatywy: blokady przez bazę danych

Tabela locks w PostgreSQL

Klasyczne podejście — dedykowana tabela dla blokad:

Google AdInline article slot
CREATE TABLE locks (
    resource VARCHAR(100) PRIMARY KEY,
    locked_by VARCHAR(100),
    locked_at TIMESTAMPTZ DEFAULT NOW(),
    expires_at TIMESTAMPTZ
);

Atomowy INSERT z obsługą konfliktów:

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;

Logika:

  • Jeśli blokada nie istnieje — tworzona jest nowa z TTL.
  • Jeśli blokada wygasła — aktualizowana.
  • Jeśli aktywna — zwracany jest pusty wynik.

Wymagania:

Google AdInline article slot
  • Unikalny resource dla operacji.
  • Unikalny locked_by (GUID lub ID instancji + ID wątku).

Poziomy izolacji

SERIALIZABLE zapewnia atomowość bez dodatkowych tabel, ale obniża przepustowość. W środowiskach enterprise administratorzy baz danych rzadko to aprobują ze względu na obciążenie. Preferowane są podejścia UPSERT z READ COMMITTED.

Porównanie podejść

| Podejście | Zalety | Wady |

|--------|-------|--------|

| Prymitywy .NET | Prostota, niska latencja | Tylko jedna instancja |

| Mutex | Synchronizacja na poziomie OS | Nie dla Docker/Linux, problemy z async |

| Blokady w BD | Integracja z bazą danych | Overhead na BD, problemy ze skalowalnością|

| RedLock.NET | Wysoka dostępność, TTL | Zależność od Redis |

Blokady w bazie danych nadają się do prostych przypadków o niskiej częstotliwości. RedLock.NET — do dużych obciążeń z gwarancjami.

Praktyczna implementacja RedLock.NET

Instalacja: dotnet add package RedLock.net. Utwórz RedLockFactory z endpointami Redis:

var multiplexer = new ConnectionMultiplexer(...);
var factory = RedLockFactory.Create(...);
using var @lock = await factory.CreateLockAsync(resource, expiry);
if (@lock.IsAcquired) {
    // critical section
}

Kluczowe parametry:

  • resource: ciąg identyfikatora (klucz w Redis).
  • expiry: TimeSpan dla TTL.
  • Quorum: większość węzłów Redis dla konsensusu.

Obsługa awarii: biblioteka automatycznie przedłuża blokadę przy długich operacjach za pomocą IExtensibleLock.

Skalowanie i najlepsze praktyki

  • Używaj Redis Sentinel lub Cluster dla wysokiej dostępności.
  • Dryft zegara < 10 ms między węzłami.
  • Czas ważności > 2x dryft zegara + czas operacji.
  • Monitoruj wskaźnik niepowodzeń pozyskiwania blokad.

W produkcji łącz z wzorcem circuit breaker dla zewnętrznych wywołań w sekcji krytycznej.

Co ważne

  • RedLock.NET zapewnia bezpieczeństwo oparte na quorum bez pojedynczego punktu awarii.
  • Blokady w bazie danych są prostsze, ale podatne na przeciążenie bazy danych i nie skalują się.
  • Zawsze ustawiaj TTL dla ochrony przed zombie blokadami.
  • Testuj pod obciążeniem z symulacją partycji sieci.
  • Dla .NET Core+ unikaj Mutex w kontenerach.

— Editorial Team

Advertisement 728x90

Czytaj dalej