ASP.NET Core 6/7 Web API简单请求下CPU近100%占用排查求助
ASP.NET Core 迁移后 Redis 相关 CPU 占用过高问题排查与解决
背景情况
- 原服务为基于 Node.js(Express)的轻量 REST API,仅从 Redis 读取数据返回响应,高负载下 CPU 峰值可控
- 迁移至 ASP.NET Core 后,相同负载下 CPU 占用直接翻倍,导致 AWS 自动扩容频繁触发,甚至仅达原 Node.js 服务一半负载时就触发扩容
- 使用 StackExchange.Redis 作为 Redis 客户端,且已按最佳实践将
ConnectionMultiplexer配置为单例实例
追踪数据分析(结合截图场景)
从提供的追踪数据来看,CPU 消耗主要集中在两个核心环节:
- 同步 Redis 调用导致的线程池开销:若请求处理中使用同步阻塞方式调用 Redis(如
.Wait()、.Result),会占用线程池线程,迫使线程池频繁创建新线程,带来大量上下文切换开销,直接推高 CPU - 序列化/反序列化的高开销:默认序列化器(如 Newtonsoft.Json)在处理大量数据时,若未做性能优化,会产生显著的 CPU 消耗
- 此外,StackExchange.Redis 连接配置不合理、Redis 命令使用低效,也可能是诱因
针对性优化方案
1. 全面改用异步 Redis 操作
彻底替换同步调用为异步方法,通过 await 非阻塞调用,避免线程池资源被占用:
// 错误:同步阻塞调用 var redisValue = _redisDatabase.StringGet("user:1001").Result; // 正确:异步非阻塞调用 var redisValue = await _redisDatabase.StringGetAsync("user:1001");
2. 优化序列化机制
替换为高性能序列化器,或调整现有序列化器的配置,减少 CPU 消耗:
- 使用
System.Text.Json替代 Newtonsoft.Json,并开启性能优化选项:
var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, WriteIndented = false, ReferenceHandler = ReferenceHandler.IgnoreCycles, DefaultBufferSize = 1024, // 启用缓冲区池化 AllowTrailingCommas = true }; var userData = JsonSerializer.Deserialize<User>(redisValue.ToString(), jsonOptions);
- 进一步优化可使用二进制序列化器(如 MessagePack),大幅降低序列化 CPU 开销
3. 调整 StackExchange.Redis 连接配置
初始化 ConnectionMultiplexer 时,优化关键参数减少不必要的开销:
var redisConfig = new ConfigurationOptions { EndPoints = { "your-redis-endpoint:6379" }, SyncTimeout = 5000, // 合理设置同步超时,避免长时间阻塞 AsyncTimeout = 5000, ConnectTimeout = 10000, PooledSocketTimeout = 5000, // 池化套接字超时,减少连接重建 AbortOnConnectFail = false, MaxIdleConnections = 10, // 配置连接池大小,避免过度创建连接 MaxTotalConnections = 50, // 禁用不必要的命令追踪,减少额外开销 CommandMap = CommandMap.Default }; var multiplexer = ConnectionMultiplexer.Connect(redisConfig);
4. 优化 Redis 命令使用
- 避免使用
KEYS *、SCAN等全库遍历命令,改用前缀哈希表、有序集合等方式组织数据 - 将多个小的 Redis 操作合并为批量命令,减少网络往返次数:
// 批量获取多个键值 var keys = new RedisKey[] { "user:1001", "user:1002", "user:1003" }; var values = await _redisDatabase.StringGetAsync(keys);
5. 启用客户端监控
通过 StackExchange.Redis 的内置事件,追踪命令执行耗时,定位具体高开销操作:
multiplexer.CommandExecuted += (sender, args) => { if (args.DurationMs > 100) // 记录耗时超过100ms的命令 { Console.WriteLine($"高耗时Redis命令: {args.Command}, 耗时: {args.DurationMs}ms"); } };
内容的提问来源于stack exchange,提问作者Amit Singh
相关产品推荐
相关产品推荐

