ABP VNext + Redis Bloom Filter:大规模缓存穿透防护与请求去重 🚀
📚 目录
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 Behavior
context.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. 可能存在,直接查询缓存/DB
return 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