ABP VNext + Redis Bloom Filter:大规模缓存穿透防护与请求去重
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