You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免Event Store DB客户端持续内存占用增长?

Event Store .NET客户端内存泄漏问题及解决方案

问题描述

我在使用Event Store .NET客户端时遇到内存泄漏问题:将客户端以单例模式注册到.NET依赖注入后,长时间运行应用时,内存占用会随每个订阅持续增长。我的应用存在大量短流场景。

客户端注册方式

我按以下方式创建并注册客户端,可复现问题的最小化应用已准备:

var esdbConnectionString = configuration.GetValue("ESDB_CONNECTION_STRING", "esdb://admin:changeit@localhost:2113?tls=false");
var eventStoreClientSettings = EventStoreClientSettings.Create(esdbConnectionString);
var eventStoreClient = new EventStoreClient(eventStoreClientSettings);
services.AddSingleton(eventStoreClient);

复现步骤

  • 按照文档推荐,将EventStoreClient注册为单例。
  • 长期订阅大量流。
  • 取消传入流订阅的CancellationToken并等待其被垃圾回收。
  • 观察服务内存占用持续增长。

流创建与订阅代码

我创建和订阅流的方式如下:

var streamName = CreateStreamName();
var payload = new PingEvent { StreamNr = _currentStreamNumber };
var eventData = new EventData(Uuid.NewUuid(), typeof(PingEvent).Name, EventSerialization.SerializeEventData(payload));
await _client.AppendToStreamAsync(streamName, StreamState.Any, new[] { eventData });

var streamCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(30));

await _client.SubscribeToStreamAsync(streamName, FromStream.Start, async (sub, evnt, token) =>
{
    if (evnt.Event.EventType == "PongEvent")
    {
        _previousStreamIsDone = true;
        streamCancellationTokenSource.Cancel();
    }
},
cancellationToken: streamCancellationTokenSource.Token);

已尝试的方案

  • 注册为Transient或Scoped:将客户端注册为Transient或Scoped时,.NET DI内部会抛出大量异常,引发诸多问题。
  • 手动管理客户端生命周期:通过单例服务定期销毁并创建新客户端,确保同一时间仅存在一个实例,但结果与注册为Transient或Scoped时相同。

内存增长原因分析

通过内存dump分析,发现内存增长源于gRPC客户端中的*ActiveCalls* HashSet,订阅取消后相关资源未被正确释放。

解决方案

1. 显式释放订阅资源

使用SubscribeToStreamAsync返回的EventStoreSubscription对象,在订阅取消后主动调用DisposeAsync(),确保释放gRPC调用资源:

var streamCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(30));
var subscription = await _client.SubscribeToStreamAsync(streamName, FromStream.Start, async (sub, evnt, token) =>
{
    if (evnt.Event.EventType == "PongEvent")
    {
        _previousStreamIsDone = true;
        streamCancellationTokenSource.Cancel();
    }
}, cancellationToken: streamCancellationTokenSource.Token);

// 等待订阅完成或取消后释放资源
try
{
    await subscription.WaitForCompletionAsync(streamCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
    // 预期的取消操作,无需处理
}
finally
{
    await subscription.DisposeAsync();
}

2. 优化gRPC通道配置

在创建EventStoreClientSettings时,配置gRPC通道的空闲超时,避免闲置连接占用资源:

var esdbConnectionString = configuration.GetValue("ESDB_CONNECTION_STRING", "esdb://admin:changeit@localhost:2113?tls=false");
var eventStoreClientSettings = EventStoreClientSettings.Create(esdbConnectionString);

// 配置通道空闲超时与保活参数
eventStoreClientSettings.ChannelOptionsActions.Add(options =>
{
    options.HttpHandler = new SocketsHttpHandler
    {
        PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
        KeepAlivePingDelay = TimeSpan.FromMinutes(1),
        KeepAlivePingTimeout = TimeSpan.FromSeconds(30)
    };
});

var eventStoreClient = new EventStoreClient(eventStoreClientSettings);
services.AddSingleton(eventStoreClient);

3. 升级客户端版本

检查Event Store .NET客户端的最新版本,后续版本可能已修复gRPC连接泄漏相关问题(当前使用版本为22.0.0)。

内容的提问来源于stack exchange,提问作者Espen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 21:15:33