ABP VNext + Redis Bloom Filter:大規模緩存穿透防護與請求去重 🚀
📚 目錄
- ABP VNext + Redis Bloom Filter:大規模緩存穿透防護與請求去重 🚀
- TL;DR ?
- 1. 引言 🎉
- 2. 環境與依賴 🛠?
- 3. Bloom Filter 原理簡述 🎓
- 4. ABP 中的配置化注入 🔧
- 4.1 配置類
- 4.2 Module 注冊
- 5. 請求去重 Behavior 🔐
- 5.1 Key 生成:SHA-256 哈希
- 5.2 自定義異常
- 5.3 Behavior 實現
- 6. 緩存穿透防護策略 🔍
- 7. Prometheus 全鏈路監控 📈
- 7.1 中間件配置
- 7.2 自定義指標
- 8. 從啟動到攔截 🚀
TL;DR ?
- 在 ABP VNext 應用層,通過
BloomFilter.Redis.NetCore
+StackExchange.Redis
實現高性能、可配置的請求去重與緩存穿透防護 (NuGet) - 支持
IOptionsMonitor<BloomSettings>
動態調整預估容量、誤判率與 Filter Key - 使用 SHA-256 生成緊湊穩定的請求 Key 并結合
ContainsAsync
+AddAsync
避免并發競態 - 自定義
DuplicateRequestException
映射 HTTP 409,嵌入日志(ILogger)與 Prometheus 指標,全鏈路可觀測 (NuGet)
1. 引言 🎉
在高并發場景下,比如秒殺、驗證碼、消息冪等等業務,常會遇到:
- 緩存穿透:惡意或無效 Key 直擊數據庫,拖垮后端 😱
- 請求去重:同一業務上下文多次觸發同一邏輯,浪費計算與 I/O 🔄
如何在 ABP VNext (.NET 7/8) 中:
- 請求去重:管道最前端攔截重復請求
- 緩存穿透防護:訪問 DB/緩存前快速判斷 Key 是否“可能存在”
并在生產級角度引入:
- 配置化 (
IOptionsMonitor<BloomSettings>
) - SHA-256 Key 生成
- ContainsAsync + AddAsync 競態處理
- Prometheus 全鏈路監控
2. 環境與依賴 🛠?
dotnet add package StackExchange.Redis
dotnet add package BloomFilter.Redis.NetCore --version 2.5.2
dotnet add package prometheus-net.AspNetCore
BloomFilter.Redis.NetCore
:Redis 后端 Bloom Filter 實現 (NuGet)prometheus-net.AspNetCore
:ASP.NET Core 集成的 Prometheus 指標服務 (NuGet)
appsettings.json 示例:
{"Redis": {"Configuration": "127.0.0.1:6379"},"BloomSettings": {"FilterKey": "bf:requests","ExpectedItems": 1000000,"FalsePositiveRate": 0.001}
}
3. Bloom Filter 原理簡述 🎓
-
結構:大小為 m 的位數組 + k 哈希函數
-
誤判率:
- k = ln2·(m/n)
- 誤判 ≈ (1–e–k·n/m)k
-
對比 LRU:
- LRU:存全量 Key,無誤判
- Bloom:低內存、高速,允許小概率誤判
4. ABP 中的配置化注入 🔧
4.1 配置類
public class BloomSettings
{public string FilterKey { get; set; }public long ExpectedItems { get; set; }public double FalsePositiveRate { get; set; }
}
4.2 Module 注冊
public override void ConfigureServices(ServiceConfigurationContext context)
{var config = context.Services.GetConfiguration();// 綁定配置Configure<BloomSettings>(config.GetSection("BloomSettings"));// 注入 Redis 連接context.Services.AddSingleton<IConnectionMultiplexer>(sp =>ConnectionMultiplexer.Connect(config["Redis:Configuration"]));// 注入 BloomFilter(FilterRedisBuilder.Build 來自 BloomFilter.Redis.NetCore)context.Services.AddSingleton<IBloomFilter>(sp =>{var settings = sp.GetRequiredService<IOptionsMonitor<BloomSettings>>().CurrentValue;return FilterRedisBuilder.Build(config["Redis:Configuration"], // Redis 連接字符串settings.FilterKey, // Bloom Filter Key & 名稱(int)settings.ExpectedItems, // 預估容量settings.FalsePositiveRate // 誤判率);});// 注冊 Pipeline Behaviorcontext.Services.AddTransient(typeof(IPipelineBehavior<,>),typeof(BloomFilterBehavior<,>));
}
FilterRedisBuilder.Build
方法在 vla/BloomFilter.NetCore 庫中提供,可配置容量、誤判率并返回IBloomFilter
實例 (GitHub)。
5. 請求去重 Behavior 🔐
5.1 Key 生成:SHA-256 哈希
public static class BloomKeyGenerator
{public static string Generate(object request){var raw = $"{request.GetType().Name}:{JsonSerializer.Serialize(request, new JsonSerializerOptions {IgnoreNullValues = true})}";using var sha = SHA256.Create();var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(raw));return Convert.ToHexString(hash); // .NET 5+ API}
}
🔍 提示:極端高吞吐場景可選用 xxHash 等非加密哈希,進一步降 CPU 開銷 (GitHub)。
5.2 自定義異常
public class DuplicateRequestException : BusinessException
{public DuplicateRequestException(): base("DUPLICATE_REQUEST", "請求已被攔截(重復請求)") { }
}
全局異常處理中映射為 HTTP 409 Conflict。
5.3 Behavior 實現
public class BloomFilterBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>where TRequest : IRequest<TResponse>
{private readonly IBloomFilter _bloom;private readonly ILogger _logger;private static readonly Counter ContainsCount = Metrics.CreateCounter("bloom_contains_total", "BloomFilter ContainsAsync 調用總數");private static readonly Counter AddCount = Metrics.CreateCounter("bloom_add_total", "BloomFilter AddAsync 調用總數");private static readonly Counter DuplicateCount = Metrics.CreateCounter("bloom_duplicate_total","重復請求攔截總數");public BloomFilterBehavior(IBloomFilter bloomFilter,ILogger<BloomFilterBehavior<TRequest, TResponse>> logger){_bloom = bloomFilter;_logger = logger;}public async Task<TResponse> Handle(TRequest request,RequestHandlerDelegate<TResponse> next,CancellationToken cancellationToken){var key = BloomKeyGenerator.Generate(request);ContainsCount.Inc();if (await _bloom.ContainsAsync(key)){DuplicateCount.Inc();_logger.LogWarning("重復請求攔截: {Key}", key);throw new DuplicateRequestException();}AddCount.Inc();await _bloom.AddAsync(key);return await next();}
}
如所用庫的
AddAsync
返回布爾值,可用返回值判斷“新加”/“已存在”,進一步簡化邏輯。
6. 緩存穿透防護策略 🔍
public async Task<ProductDto> GetAsync(Guid productId)
{var key = productId.ToString();// 1. Bloom 預判斷if (!await _bloom.ContainsAsync(key)){// 未命中,嘗試真實查詢并預熱var entity = await _repository.GetAsync(productId);if (entity != null){await _bloom.AddAsync(key);}return entity; // null 或真實結果}// 2. 可能存在,直接查詢緩存/DBreturn await _repository.GetAsync(productId);
}
?? 優化:此策略無需離線“預熱”,首訪合法請求正常命中并寫入 Bloom。
7. Prometheus 全鏈路監控 📈
7.1 中間件配置
var builder = WebApplication.CreateBuilder(args);// 注冊 Prometheus 指標服務 & HTTP 埋點
builder.Services.AddMetricServer();
builder.Services.AddHttpMetrics();var app = builder.Build();
app.UseHttpMetrics(); // 自動統計 HTTP 請求
app.UseMetricServer(); // 暴露 /metrics
app.MapControllers();
app.Run();
Prometheus 客戶端包:
prometheus-net.AspNetCore
(NuGet)。
7.2 自定義指標
bloom_contains_total
bloom_add_total
bloom_duplicate_total
false_positive_total
(業務層捕獲誤判并上報)
8. 從啟動到攔截 🚀
docker run -d --name redis -p 6379:6379 redis:7
dotnet run --project YourAbpApp