【问题标题】:How do I use Event Store DB client without continued memory usage growth?如何在内存使用量不持续增长的情况下使用 Event Store DB 客户端?
【发布时间】:2022-12-10 14:02:18
【问题描述】:

我正在使用event store client for .Net,我正在努力寻找使用客户端的正确方法。当我在 .Net 依赖注入中将客户端注册为单例并在一段较长的时间内运行我的应用程序时,内存使用量随着每次订阅而不断增长。

我通过以下方式创建和注册客户端。可以找到遇到问题的完整最小应用程序here.

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);

我的应用程序在很长一段时间内有大量短流

重现

重现该行为的步骤:

  1. 按照 documentation 中的建议将 EventStore 客户端注册为单例。
  2. 在较长时间内订阅大量流。
  3. 取消发送到流订阅的CancellationToken,让它被垃圾回收。
  4. 观察服务的内存使用量增长。

    我如何创建和订阅流:

    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如果我在 .Net DI 中将客户端注册为 Transient 或 Scoped,它会在内部抛出数千个异常并导致多个问题。

    手动处理客户端的生命周期通过拥有处理客户端生命周期的单例服务,我尝试每隔一段时间处理一次客户端并创建一个新客户端,以确保同时只存在一个客户端实例。这会导致与将服务注册为 Transient 或 Scoped 相同的问题。

    I am using version 22.0.0 of the Event Store client 在 .Net 6 中针对事件存储数据库 21.10.0。在 Windows 和标准 aspnet:6.0 linux docker 容器上运行时都会出现问题。

    通过检查 these dotnet-dumps 的结果,内存增长似乎发生在 gRPC 客户端的 this HashSet of ActiveCalls 内部。

    我希望找到一种不会导致内存增长的使用客户端的方法。

【问题讨论】:

    标签: dependency-injection grpc .net-6.0 eventstoredb grpc-c#


    【解决方案1】:

    在您的复制品中,泄漏的调用来自您在处理订阅中收到的事件时发出的额外读取。

    目前有一个未解决的问题 (https://github.com/EventStore/EventStore-Client-Dotnet/issues/219) 可以更好地处理这个问题,但目前如果您发出读取但不消耗所有事件并且不取消读取,则调用保持打开状态。在您的情况下,如果从服务器在主服务器发出读取结果之前设法回复Pong,就会发生这种情况,该读取是在订阅中接收到自己的Ping。该读取将包含 Ping 和 Pong,只有 Ping 被读取,并且调用保持打开状态。

    现在,如果您通过将要取消的取消令牌传递到 ReadFromStartOfStreamToEnd 中的 ReadStreamAsync 调用来取消这些读取,它应该可以解决您的问题。

    如果对您有帮助,您可以看到Current Calls 的实时数量,而不是等待很长时间才能看到对内存的影响:

    dotnet-counters monitor --counters "Grpc.Net.Client" -p <processid>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-02
      • 1970-01-01
      相关资源
      最近更新 更多