Semaphore - Distributed Permit-Based Concurrency¶
Semaphore limits concurrent access to a shared resource with a configurable number of permits.
Overview¶
| Property | Value |
|---|---|
| Type | Counting Semaphore |
| Permits | Configurable (1 to N) |
| Fairness | Non-fair (first-come basis) |
| Expiration | Auto-release on timeout |
How It Works¶
sequenceDiagram
participant C1 as Client 1
participant C2 as Client 2
participant C3 as Client 3
participant C4 as Client 4
participant Redis as Redis (permits=3)
Note over Redis: Available: 3
C1->>Redis: ZADD semaphore ts1 permit_1
Redis-->>C1: Permit granted
Note over Redis: Used: 1, Available: 2
C2->>Redis: ZADD semaphore ts2 permit_2
Redis-->>C2: Permit granted
Note over Redis: Used: 2, Available: 1
C3->>Redis: ZADD semaphore ts3 permit_3
Redis-->>C3: Permit granted
Note over Redis: Used: 3, Available: 0
C4->>Redis: ZCARD semaphore
Redis-->>C4: 3 (full)
Note over C4: No permits, waiting...
rect rgb(200, 230, 200)
Note over C1,C3: All 3 clients working concurrently
end
C1->>Redis: ZREM semaphore permit_1
Note over Redis: Used: 2, Available: 1
C4->>Redis: ZADD semaphore ts4 permit_4
Redis-->>C4: Permit granted
Note over Redis: Used: 3, Available: 0
Note over C4: Working...
Key Concepts¶
Sorted Set for Permits¶
Each permit is a member with timestamp score:
Enables efficient count and expiration cleanup.Permit Count Check¶
local count = redis.call("ZCARD", semaphore_key)
if count < max_permits then
redis.call("ZADD", semaphore_key, timestamp, permit_id)
return 1 -- acquired
end
return 0 -- no permits available
Automatic Expiration Cleanup¶
Stale permits are removed before count check:
Mode Differences
The diagram above shows the conceptual flow. In multi-node mode:
- Permit set (ZADD) is replicated to all nodes
- Permit acquisition requires quorum (N/2+1 nodes)
- Available permit count is aggregated across nodes
- Clock drift is applied to permit validity time
Usage¶
RedlockSemaphore semaphore = redlockManager.createSemaphore("api-limiter", 5);
// Try to acquire permit
if (semaphore.tryAcquire(Duration.ofSeconds(5))) {
try {
callRateLimitedAPI();
} finally {
semaphore.release();
}
}
// Acquire multiple permits
if (semaphore.tryAcquire(3, Duration.ofSeconds(5))) {
try {
performBulkOperation();
} finally {
semaphore.release(3);
}
}
Use Cases¶
| Scenario | Permits | Purpose |
|---|---|---|
| API rate limit | 10/sec | Prevent quota exhaustion |
| DB connection pool | 20 | Limit concurrent connections |
| File uploads | 5 | Control bandwidth usage |
| Batch jobs | 3 | Prevent resource starvation |
Supported Modes¶
| Mode | Supported | Notes |
|---|---|---|
| Single Node | Yes | Permit set on single instance |
| Multi-Node (Quorum) | Yes | Permits tracked per node, quorum required |
Configuration¶
| Parameter | Default | Description |
|---|---|---|
maxPermits |
(required) | Maximum concurrent permits |
permitTimeoutMs |
30000 | Auto-release timeout |
acquisitionTimeoutMs |
10000 | Max wait time for permit |
Methods¶
| Method | Description |
|---|---|
acquire() |
Block until permit available |
tryAcquire(Duration) |
Try with timeout |
tryAcquire(int, Duration) |
Try for multiple permits |
release() |
Release one permit |
release(int) |
Release multiple permits |
availablePermits() |
Current available count |
When to Use¶
Good for: - Rate limiting API calls - Connection pooling - Resource throttling - Parallel task limiting
Consider alternatives for: - Single exclusive access → Redlock - Read-heavy workloads → ReadWriteLock
See Also¶
- Redlock - Exclusive locking (permits=1)
- CountDownLatch - Coordination primitive