redlock.ts

Software Design Document

Introduction and Overview

This document describes the design of a Redlock implementation in TypeScript. The system is designed to provide a distributed lock using the Redis database as the backend. The Redlock algorithm is used to ensure that multiple clients can acquire locks on the same resource without deadlocks or race conditions.

System Architecture

The system consists of several components:

Data Design

The data design involves the use of keys in the Redis database to represent locks. Each key consists of three parts: the resource being locked, the lock value (a unique string), and the time-to-live for the lock. The Redlock algorithm uses these keys to ensure that only one client can acquire a lock on a particular resource at a time.

Interface Design

The RedisClient interface defines the methods for interacting with a Redis database:

The Redlock class provides the following methods for acquiring, releasing, and renewing locks:

Component Design

The Redlock class is responsible for managing the acquisition, release, and renewal of locks using the Redlock algorithm. The class takes an array of RedisClient objects in its constructor, which it uses to attempt lock operations across all clients. The class also has a retry count and a retry delay that can be configured during initialization.

The RedisClient interface defines the methods for interacting with a Redis database, including setting keys with a unique value and evaluating Lua scripts.

User Interface Design

There is no user interface for this system as it is designed to be used programmatically by other applications that need to acquire locks using the Redlock algorithm.

Assumptions and Dependencies

Glossary of Terms

Class Diagram (in Mermaid syntax)

class RedisClient {
    set(resource: string, value: string, options: SetOptions): Promise<SetResult>
    eval(script: string, keys: string[], args: any[]): Promise<EvalResult>
}

class Redlock {
    private clients: RedisClient[];
    private retryCount: number;
    private retryDelay: number;
    private driftFactor: number;
    
    constructor(clients: RedisClient[], retryCount = 3, retryDelay = 200, driftFactor = 0.01) {
        this.clients = clients;
        this.retryCount = retryCount;
        this.retryDelay = retryDelay;
        this.driftFactor = driftFactor;
    }
    
    private async acquireLockInstance(client: RedisClient, resource: string, value: string, ttl: number): Promise<boolean> { ... }
    
    private async releaseLockInstance(client: RedisClient, lock: Lock): Promise<number> { ... }
    
    public async acquireLock(resource: string, ttl: number): Promise<Lock | null> { ... }
    
    public async acquireLockWithCustomRetry(resource: string, ttl: number, retryStrategy: (attempt: number) => number): Promise<Lock | null> { ... }
    
    private async tryAcquire(resource: string, value: string, ttl: number): Promise<number> { ... }
    
    public async releaseLock(resource: string, value: string): Promise<void> { ... }
    
    public async renewLock(resource: string, value: string, ttl: number): Promise<boolean> { ... }
    
    private sleep(ms: number): Promise<void> { ... }
}