Azure WebPubSub发送消息引发内存泄漏问题求助
问题概述
Web App运行一段时间后因内存不足重启,排查发现使用Azure WebPubSub发送消息后内存持续增长。并发发送100条消息时,HttpConnectionPool会创建100个连接导致非托管内存骤增;后续发送虽复用连接,但堆内存仍有大量分配,仅调用GC.Collect()能临时缓解。
关键问题解答
1. HttpConnectionPool连接存活时长
Azure WebPubSub SDK底层依赖HttpClient的连接池,默认配置下:
- 空闲连接的存活时长为2分钟(对应
ServicePointManager.DefaultConnectionLeaseTimeout,或Azure Core中HttpClientOptions.ConnectionLeaseTimeout)。 - 并发请求时,连接池会根据请求数量创建新连接,直到达到
MaxConnectionsPerServer上限(默认无硬限制,实际受系统资源约束)。空闲连接超过存活时长后,会被自动回收释放。
2. 堆内存存储的数据及释放方式
堆内存主要分配以下对象:
- 请求/响应的
RequestContent实例及序列化后的消息内容 - Azure SDK的请求上下文对象(如
OperationContext、请求追踪数据) - HttpClient的请求消息、响应消息实例
- 异常对象(若请求失败产生
RequestFailedException)
这些均为托管对象,默认由.NET GC自动回收。但高并发场景下,短生命周期对象会快速积累,直到GC触发Gen0/Gen1回收。若存在未释放的引用(如未完成的Task、未取消的事件注册),会导致对象无法被回收,形成内存泄漏。
3. 根本解决方案
(1)全局复用WebPubSubServiceClient
WebPubSubServiceClient是线程安全的,应作为全局单例使用,避免每次请求创建新实例。每个client实例自带独立的HttpClient连接池,复用能有效共享连接,减少连接创建开销。
(2)自定义连接池配置
通过WebPubSubClientOptions配置HttpClient参数,限制并发连接数、缩短空闲连接存活时间:
using Azure.Core; using Azure.Messaging.WebPubSub; using System.Net.Http; var connectionString = "<ConnectionString>"; var hub = "<HubName>"; var clientOptions = new WebPubSubClientOptions(); clientOptions.HttpClient = new HttpClient(new HttpClientHandler { MaxConnectionsPerServer = 20, // 限制单服务器并发连接数 ConnectionLeaseTimeout = 30 * 1000 // 空闲连接30秒后回收 }); // 全局单例的client var serviceClient = new WebPubSubServiceClient(connectionString, hub, clientOptions);
(3)优化请求内容的生命周期
用using包裹RequestContent,确保对象及时释放,减少堆内存占用:
Task[] tasks = new Task[100]; for (int i = 0; i < 100; i++) { using var content = RequestContent.Create("Message"); tasks[i] = serviceClient.SendToUserAsync("testUser", content, ContentType.TextPlain); } Task.WaitAll(tasks);
(4)批量发送消息
使用批量接口减少请求数量,降低连接池压力:
// 批量发送给多个用户 var userIds = new List<string> { "testUser1", "testUser2", ... }; await serviceClient.SendToUsersAsync(userIds, RequestContent.Create("Batch Message"), ContentType.TextPlain);
(5)升级Azure SDK版本
确保使用最新版的Azure.Messaging.WebPubSub NuGet包,旧版本可能存在已知的内存泄漏bug,升级可修复此类问题。
(6)避免强制调用GC.Collect()
强制GC会打断正常的垃圾回收节奏,影响应用性能。优化对象生命周期和连接池配置后,依赖.NET自动GC即可有效回收内存。
内容的提问来源于stack exchange,提问作者Oleksandr

