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.
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:
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:
- Unikalny
resourcedla 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:TimeSpandla 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
Brak komentarzy.