DistributedLeasing.Azure.Redis
5.1.0
dotnet add package DistributedLeasing.Azure.Redis --version 5.1.0
NuGet\Install-Package DistributedLeasing.Azure.Redis -Version 5.1.0
<PackageReference Include="DistributedLeasing.Azure.Redis" Version="5.1.0" />
<PackageVersion Include="DistributedLeasing.Azure.Redis" Version="5.1.0" />
<PackageReference Include="DistributedLeasing.Azure.Redis" />
paket add DistributedLeasing.Azure.Redis --version 5.1.0
#r "nuget: DistributedLeasing.Azure.Redis, 5.1.0"
#:package DistributedLeasing.Azure.Redis@5.1.0
#addin nuget:?package=DistributedLeasing.Azure.Redis&version=5.1.0
#tool nuget:?package=DistributedLeasing.Azure.Redis&version=5.1.0
DistributedLeasing.Azure.Redis
Azure Redis distributed leasing provider for .NET using the Redlock algorithm
This package implements distributed leasing using the Redlock algorithm on Azure Cache for Redis. It provides the lowest-latency lease coordination with high-performance in-memory operations.
Features
✅ Redlock Algorithm - Industry-standard distributed locking
✅ Ultra-Low Latency - Sub-millisecond operations (< 5ms typical)
✅ Automatic Renewal - Background renewal keeps leases alive
✅ Managed Identity Support - Azure Cache for Redis authentication
✅ High Throughput - Thousands of operations per second
✅ Connection Resilience - Automatic reconnection and failover
When to Use Redis Leasing
Best For:
- Ultra-low latency requirements (< 10ms)
- High-throughput scenarios (>1000 ops/sec)
- Short-duration leases (seconds to minutes)
- Applications requiring immediate lease feedback
- Rate limiting and throttling use cases
Consider Alternatives When:
- Need global multi-region coordination → Use Azure Cosmos DB
- Prefer cloud-native Azure integration → Use Azure Blob
- Cost is primary concern (Redis has higher hosting cost)
Installation
dotnet add package DistributedLeasing.Azure.Redis
This automatically includes DistributedLeasing.Abstractions with authentication and observability support.
Quick Start
Basic Usage with Connection String
using DistributedLeasing.Azure.Redis;
// Create provider with connection string
var provider = new RedisLeaseProvider(new RedisLeaseProviderOptions
{
ConnectionString = "mycache.redis.cache.windows.net:6380,password=...,ssl=True"
});
// Create lease manager for a specific resource
var leaseManager = await provider.CreateLeaseManagerAsync("my-resource-lock");
// Acquire lease
var lease = await leaseManager.AcquireAsync(TimeSpan.FromSeconds(30));
if (lease != null)
{
try
{
// Do work while holding the lease
Console.WriteLine($"Lease acquired: {lease.LeaseId}");
await DoExclusiveWorkAsync();
}
finally
{
// Always release when done
await lease.ReleaseAsync();
}
}
else
{
Console.WriteLine("Could not acquire lease - another instance holds it");
}
Non-Blocking Acquisition
// Try to acquire without waiting
var lease = await leaseManager.TryAcquireAsync(TimeSpan.FromSeconds(30));
if (lease == null)
{
// Lease is held by another instance - fail fast
Console.WriteLine("Lease unavailable");
return;
}
// Proceed with work
High-Performance Pattern
// For short-duration, high-frequency leases
var leaseOptions = new LeaseOptions
{
DefaultLeaseDuration = TimeSpan.FromSeconds(10), // Short duration
AutoRenew = true,
AutoRenewInterval = TimeSpan.FromSeconds(6) // Frequent renewal
};
var provider = new RedisLeaseProvider(new RedisLeaseProviderOptions
{
ConnectionString = connectionString,
LeaseOptions = leaseOptions
});
// Rapid acquisition and release
for (int i = 0; i < 1000; i++)
{
var lease = await leaseManager.TryAcquireAsync();
if (lease != null)
{
await ProcessItemAsync(i);
await lease.ReleaseAsync();
}
}
Configuration
Authentication Options
Option 1: Connection String (Most Common)
var options = new RedisLeaseProviderOptions
{
ConnectionString = "mycache.redis.cache.windows.net:6380,password=...,ssl=True,abortConnect=False"
};
Option 2: Managed Identity (Azure Cache for Redis Premium)
using Azure.Identity;
var options = new RedisLeaseProviderOptions
{
ConnectionString = "mycache.redis.cache.windows.net:6380,ssl=True,abortConnect=False",
Credential = new DefaultAzureCredential()
};
Option 3: Configuration String Options
var configurationOptions = new StackExchange.Redis.ConfigurationOptions
{
EndPoints = { "mycache.redis.cache.windows.net:6380" },
Password = "your-access-key",
Ssl = true,
AbortOnConnectFail = false,
ConnectTimeout = 5000,
SyncTimeout = 5000
};
var options = new RedisLeaseProviderOptions
{
ConfigurationOptions = configurationOptions
};
For comprehensive authentication configuration, see Authentication Guide.
Provider Options
public class RedisLeaseProviderOptions
{
// Authentication (choose one)
public string? ConnectionString { get; set; }
public ConfigurationOptions? ConfigurationOptions { get; set; }
public TokenCredential? Credential { get; set; } // For managed identity
// Key prefix for lease keys
public string KeyPrefix { get; set; } = "lease:";
// Lease configuration
public LeaseOptions? LeaseOptions { get; set; }
// Custom metadata
public IDictionary<string, string>? Metadata { get; set; }
}
Lease Options
var leaseOptions = new LeaseOptions
{
DefaultLeaseDuration = TimeSpan.FromSeconds(30), // Redis optimized for shorter leases
AutoRenew = true,
AutoRenewInterval = TimeSpan.FromSeconds(20), // 2/3 of duration
MaxRetryAttempts = 3,
RetryDelay = TimeSpan.FromMilliseconds(100) // Fast retry for Redis
};
var providerOptions = new RedisLeaseProviderOptions
{
ConnectionString = connectionString,
LeaseOptions = leaseOptions
};
How Redis Leasing Works
Redlock Algorithm
This implementation uses the Redlock algorithm created by Redis author Salvatore Sanfilippo:
- Acquire: Set key with unique lease ID using
SET key value NX PX millisecondsNX: Only set if key doesn't existPX: Set expiration in milliseconds
- Validate: Check operation success and compute elapsed time
- Renew: Use Lua script to atomically check and extend expiration
- Release: Use Lua script to atomically check lease ID and delete key
Key Characteristics:
- Atomic Operations: Lua scripts ensure atomicity
- Time-Based Expiration: Redis automatically expires keys
- No Polling: Direct get/set operations
- High Performance: In-memory operations
Redis Key Structure
Leases are stored as Redis keys with the pattern:
lease:{leaseKey}
Example:
- Lease key:
database-migration - Redis key:
lease:database-migration
Value stored:
{
"leaseId": "abc123-def456-ghi789",
"ownerId": "instance-01",
"acquiredAt": "2025-12-25T12:00:00Z",
"metadata": {
"instance": "server-01",
"version": "1.0.0"
}
}
Performance Characteristics
Latency
| Operation | Typical Latency | Notes |
|---|---|---|
| Acquire (success) | 1-5ms | Same Azure region |
| Acquire (failure) | 1-5ms | Instant NX check |
| Renew | 1-5ms | Lua script execution |
| Release | 1-5ms | Lua script delete |
Network Dependency: Latency increases with distance from Redis instance.
Throughput
- Single Lease: 10,000+ operations/second
- Multiple Leases: Scales linearly (independent keys)
- Connection Multiplexing: StackExchange.Redis handles connection pooling
Best Practice: Redis excels at high throughput - ideal for rate limiting and short-duration locks.
Lease Duration Recommendations
- Minimum: 5 seconds (avoid excessive network overhead)
- Typical: 10-30 seconds (balanced responsiveness and overhead)
- Maximum: 300 seconds (5 minutes - though Redis better suited for shorter)
Best Practices
1. Use Premium Tier for Production
Azure Cache for Redis Tiers:
- Basic: Single node, no SLA (development only)
- Standard: Two nodes, 99.9% SLA (production)
- Premium: Clustering, VNet, geo-replication (high availability)
2. Configure Connection Resilience
var configOptions = new ConfigurationOptions
{
EndPoints = { "mycache.redis.cache.windows.net:6380" },
Ssl = true,
AbortOnConnectFail = false, // ✅ Don't fail on initial connect failure
ConnectRetry = 3, // ✅ Retry connection attempts
ConnectTimeout = 5000, // 5 seconds
SyncTimeout = 5000, // 5 seconds
ReconnectRetryPolicy = new ExponentialRetry(1000) // ✅ Exponential backoff
};
3. Use Appropriate Lease Duration
// ✅ For high-frequency operations (rate limiting)
var lease = await leaseManager.AcquireAsync(TimeSpan.FromSeconds(5));
// ✅ For moderate-duration tasks
var lease = await leaseManager.AcquireAsync(TimeSpan.FromSeconds(30));
// ⚠️ For very long tasks, consider Blob or Cosmos
var lease = await leaseManager.AcquireAsync(TimeSpan.FromMinutes(5));
4. Always Release Leases
// ✅ Good
var lease = await leaseManager.AcquireAsync();
try
{
await DoWorkAsync();
}
finally
{
await lease.ReleaseAsync(); // Always release
}
5. Handle Connection Failures
var cts = new CancellationTokenSource();
lease.LeaseLost += (sender, e) =>
{
Console.WriteLine($"Lease lost: {e.Reason}");
if (e.Exception != null)
{
Console.WriteLine($"Connection issue: {e.Exception.Message}");
}
cts.Cancel(); // Stop work on connection loss
};
try
{
await LongRunningWorkAsync(cts.Token);
}
finally
{
await lease.ReleaseAsync();
}
6. Use Key Prefixes for Organization
var options = new RedisLeaseProviderOptions
{
ConnectionString = connectionString,
KeyPrefix = "myapp:leases:" // Organize keys by application
};
// Results in keys like: myapp:leases:my-resource-lock
Redlock Algorithm Details
Safety Properties
The Redlock algorithm provides:
- Mutual Exclusion: At most one client can hold the lock at any time
- Deadlock Free: Eventually possible to acquire lock (via expiration)
- Fault Tolerance: Lock survives as long as majority of Redis instances available
Implementation Specifics
Atomic Renewal (Lua Script):
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("pexpire", KEYS[1], ARGV[2])
else
return 0
end
Atomic Release (Lua Script):
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
These ensure that only the lease holder can renew or release.
Troubleshooting
"Unable to connect to Redis server"
Problem: Connection string incorrect or firewall blocking.
Solution:
- Verify connection string from Azure Portal
- Check firewall rules allow your IP/VNet
- Ensure SSL port (6380) is used for Azure Cache for Redis
- Set
AbortOnConnectFail=Falsein connection string
"Timeout performing operation"
Problem: Network latency or Redis overloaded.
Solution:
var configOptions = new ConfigurationOptions
{
EndPoints = { "mycache.redis.cache.windows.net:6380" },
SyncTimeout = 10000, // Increase timeout to 10 seconds
ConnectTimeout = 10000
};
Frequent lease loss
Problem: Network instability or lease duration too short.
Solution:
var leaseOptions = new LeaseOptions
{
DefaultLeaseDuration = TimeSpan.FromSeconds(60), // Increase duration
AutoRenewInterval = TimeSpan.FromSeconds(40) // Earlier renewal
};
"WRONGTYPE Operation against a key holding the wrong kind of value"
Problem: Redis key used for different purpose previously.
Solution: Use unique key prefixes or delete the conflicting key:
redis-cli -h mycache.redis.cache.windows.net -p 6380 -a "password" DEL lease:my-resource-lock
Monitoring and Observability
Redis CLI Inspection
# Connect to Redis
redis-cli -h mycache.redis.cache.windows.net -p 6380 -a "your-access-key" --tls
# List all lease keys
KEYS lease:*
# Get lease value
GET lease:my-resource-lock
# Check TTL (time to live)
TTL lease:my-resource-lock
# Get all keys with pattern
SCAN 0 MATCH lease:* COUNT 100
Azure Portal Metrics
Monitor in Azure Portal → Cache for Redis → Metrics:
- Connected Clients: Number of active connections
- Operations/sec: Total operations per second
- Cache Hits/Misses: Performance indicators
- Server Load: CPU usage
- Network Bandwidth: In/out traffic
Metrics and Health Checks
See DistributedLeasing.Abstractions README for:
- OpenTelemetry metrics configuration
- Health check setup
- Distributed tracing integration
Architecture
┌─────────────────────────────────────────────────────┐
│ Your Application │
│ ┌──────────────┐ ┌─────────────────┐ │
│ │ILeaseProvider│────────▶│RedisLeaseProvider│ │
│ └──────────────┘ └─────────────────┘ │
│ │ │
│ │ CreateLeaseManagerAsync("my-lock") │
│ ▼ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ILeaseManager │────────▶│RedisLeaseManager │ │
│ └──────────────┘ └──────────────────┘ │
│ │ │
│ │ AcquireAsync() │
│ ▼ │
│ ┌──────────────┐ ┌────────────┐ │
│ │ ILease │────────▶│ RedisLease │ │
│ └──────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────┘
│
│ Redis Protocol (Redlock Algorithm)
▼
┌─────────────────────────────────────────────────────┐
│ Azure Cache for Redis │
│ ┌─────────────────────────────────────────────┐ │
│ │ In-Memory Key-Value Store │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ Key: lease:my-lock │ │ │
│ │ │ Value: {leaseId, ownerId, metadata} │ │ │
│ │ │ TTL: 30 seconds │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ Operations: SET NX PX, Lua scripts for renewal │
└─────────────────────────────────────────────────────┘
Redis Cluster Support
For Redis Cluster deployments:
var configOptions = new ConfigurationOptions
{
EndPoints =
{
"node1.redis.cache.windows.net:6380",
"node2.redis.cache.windows.net:6380",
"node3.redis.cache.windows.net:6380"
},
Ssl = true,
AbortOnConnectFail = false
};
var options = new RedisLeaseProviderOptions
{
ConfigurationOptions = configOptions
};
Note: Redlock works best with single-instance Redis for simplicity. For multi-instance coordination, consider Cosmos DB.
Framework Compatibility
- .NET Standard 2.0 - Compatible with .NET Framework 4.6.1+, .NET Core 2.0+
- .NET 8.0 - Long-term support release
- .NET 10.0 - Latest release
Package Dependencies
- DistributedLeasing.Abstractions - Core framework
- StackExchange.Redis - Redis client library
- Azure.Identity - Azure authentication
Related Packages
- DistributedLeasing.Azure.Blob - Blob Storage provider (cloud-native)
- DistributedLeasing.Azure.Cosmos - Cosmos DB provider (global distribution)
- DistributedLeasing.ChaosEngineering - Testing utilities
Documentation
- GitHub Repository
- Redlock Algorithm - Official Redis documentation
- StackExchange.Redis Documentation
- Authentication Guide
License
MIT License - see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Azure.Identity (>= 1.17.1)
- DistributedLeasing.Abstractions (>= 5.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.1)
- StackExchange.Redis (>= 2.10.1)
-
net10.0
- Azure.Identity (>= 1.17.1)
- DistributedLeasing.Abstractions (>= 5.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.1)
- StackExchange.Redis (>= 2.10.1)
-
net8.0
- Azure.Identity (>= 1.17.1)
- DistributedLeasing.Abstractions (>= 5.1.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.1)
- StackExchange.Redis (>= 2.10.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on DistributedLeasing.Azure.Redis:
| Package | Downloads |
|---|---|
|
DistributedLeasing.Extensions.DependencyInjection
Dependency injection extensions for the DistributedLeasing library. Provides service registration helpers for ASP.NET Core and other DI-enabled applications. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Minor version 5.1: Added Redis distributed locking sample with comprehensive documentation, enhanced setup-resources.sh script with --project argument supporting blob/cosmos/redis selection, interactive configuration wizard for all samples. Includes RedisLeaseSample demonstrating atomic SET NX operations, lock competition, and metadata inspection. No breaking API changes - safe to upgrade from 5.0.x.