是什么导致了 Azure 事件中心 ReceiverDisconnectedException/LeaseLostException?

What is causing Azure Event Hubs ReceiverDisconnectedException/LeaseLostException?

我正在使用 EventProcessorHost 和 IEventProcessor class(称之为:MyEventProcessor)从 EventHub 接收事件。我通过 运行 我在两台服务器上的 EPH 将其扩展到两台服务器,并让它们使用相同的 ConsumerGroup 连接到集线器,但主机名是唯一的(使用机器名称)。

问题是:在 day/night 的随机时间,应用程序记录此:

Exception information: 
Exception type: ReceiverDisconnectedException 
Exception message: New receiver with higher epoch of '186' is created hence current receiver with epoch '186' is getting disconnected. If you are recreating the receiver, make sure a higher epoch is used.
  at Microsoft.ServiceBus.Common.ExceptionDispatcher.Throw(Exception exception)
  at Microsoft.ServiceBus.Common.Parallel.TaskHelpers.EndAsyncResult(IAsyncResult asyncResult)
  at Microsoft.ServiceBus.Messaging.IteratorAsyncResult`1.StepCallback(IAsyncResult result)

此异常与 LeaseLostException 同时发生,当它尝试检查点时从 MyEventProcessor 的 CloseAsync 方法抛出。 (可能是因为 ReceiverDisconnectedException 而调用了 Close?)

我认为这是由于扩展到多台机器时事件中心的自动租约管理造成的。但是我想知道我是否需要做一些不同的事情来让它工作得更干净并避免这些异常?例如:有纪元的东西?

TLDR:这种行为是绝对正常的。

为什么 Lease Management 不能顺利进行 & exception-free: 给开发商更多的控制权。

说来话长 - all-the-way 来自基础知识 EventProcessorhost(特此 EPH - 与 __consumer_offset topicKafka Consumers 所做的非常相似 - 分区所有权和检查点存储)由 Microsoft Azure EventHubs 团队自己编写 - 翻译所有 EventHubs partition receiver Gu 变成一个简单的 onReceive(Events) 回调。

EPH 用于解决 2 个一般的、主要的、well-known 问题,同时读出 high-throughput 分区流,如 EventHubs:

  1. 容错接收 pipe-line - 例如:问题的更简单版本 - 如果主机 运行ning PartitionReceiver 死了又回来了——它需要从它离开的地方恢复处理。为了记住最后一次成功处理的 EventDataEPH 使用提供给 EPH 构造函数的 blob 来存储检查点 - 当用户调用 context.CheckpointAsync() 时。最终,当主机进程终止时(例如:突然重启或遇到硬件故障和 never/comesback)- 任何 EPH 实例都可以接手此任务并从 Checkpoint 恢复。

  2. Balance/distribute 个跨 EPH 个实例的分区 - 比方说,如果有 10 个分区和 2 个 EPH 个实例处理来自这 10 个分区的事件——我们需要一种方法来跨实例划分分区(EPH 库的 PartitionManager 组件执行此操作)。我们使用 Azure Storage - Blob LeaseManagement-feature 来实现。从版本 2.2.10 开始 - 为了简化问题,EPH 假定 所有分区均等加载

有了这个,让我们试着看看发生了什么: 因此,首先,在上面的 10 事件中心分区和 2 EPH 实例处理事件的示例中:

  1. 假设第一个 EPH 实例 - EPH1 启动,at-first,单独和 start-up 的一部分,它为所有 10 个分区创建接收器并正在处理事件。在启动时 - EPH1 将通过获取代表这些 10 事件中心分区的 10 存储 blob 的租约来宣布它拥有所有这些 10 分区(使用标准 nomenclature- EPH 在存储帐户中内部创建 - 从 StorageConnectionString 传递到 ctor)。租约将为 acquired for a set time,之后 EPH 实例将失去对该分区的所有权。
  2. EPH1 不断 announces 偶尔 - 它仍然拥有这些分区 - 通过 renewing 租用 blob。可以使用 PartitionManagerOptions
  3. 执行 renewal 的频率以及其他有用的调整
  4. 现在,比方说,EPH2 启动了 - 您还向 EPH2ctor 提供了与 EPH1 相同的 AzureStorageAccount。现在,它有 0 个分区要处理。因此,为了在 EPH 个实例之间实现分区平衡,它将继续 download 所有 leaseblobs 的列表,其中具有 ownerpartitionId 的映射.由此,它将 STEAL 租赁 以公平分享 partitions - 在我们的示例中为 5,并将公布有关该信息lease blob。作为其中的一部分,EPH2 读取由 PartitionX 写入的最新检查点,它想要窃取租约并继续并创建相应的 PartitionReceiverEPOCH 相同Checkpoint 中的那个。
  5. 因此,EPH1 将失去这 5 个 partitions 的所有权,并将 运行 根据其所处的确切状态陷入不同的错误。
    • 如果 EPH1 实际上正在调用 PartitionReceiver.Receive() 调用 - 而 EPH2 在同一个接收器上创建 PartitionReceiver - EPH1 将经历 ReceiverDisconnectedException。这最终将调用 IEventProcessor.Close(CloseReason=LeaseLost)。请注意,如果接收到的消息更大或 PrefetchCount 更小,则命中此特定异常的可能性更高 - 因为在这两种情况下,接收方将执行更积极的 I/O.
    • 如果EPH1处于checkpointingleaserenewinglease的状态,而EPH2stole 租约时,EventProcessorOptions.ExceptionReceived 事件处理程序将用 leaselostException 发出信号(在 leaseblob 上有 409 冲突错误)——这最终也会调用 IEventProcess.Close(LeaseLost)

为什么租赁管理不能顺利 & exception-free:

为了让消费者保持简单和 error-free,与租赁管理相关的异常可能已经被 EPH 吞没并且根本不通知 user-code。然而,我们意识到,抛出 LeaseLostException 可以使客户能够在 IEventProcessor.ProcessEvents() 回调中找到有趣的错误 - 其症状是 - 频繁 partition-moves

  • 特定机器上的轻微网络中断 - 因此 EPH1 无法 renew 租用并恢复! - 想象一下,如果这台机器的 n/w 不稳定一天 - EPH 个实例将与 ping-pong 一起玩 Partitions!这台机器将不断尝试窃取其他机器的租约 - 这在 EPH point-of-view 是合法的 - 但是,对于 EPH 的用户来说是一场彻底的灾难 - 因为它完全干扰了处理管道。 EPH - 当 n/w 在这个易碎的 m/c 上重新出现时,会准确地看到 ReceiverDisconnectedException!我们认为最好的也是唯一的方法就是让开发人员闻到这个味道!
  • 或一个简单的场景,例如 ProcessEvents 逻辑中存在错误 - 抛出未处理的致命异常并导致整个过程中断 - 例如:中毒事件。这个分区会经常移动。
  • 客户,在 EPH 也在使用的同一存储帐户上执行 write/delete 操作 - 错误(如自动 clean-up 脚本)等
  • 最后但并非最不重要的 - 我们永远不希望发生 - 在 Azure d.c 上说 5 分钟 outage,其中特定 EventHub.Partition 位于 - 说 n/w 事件。分区将在 EPH 个实例之间移动。

基本上,在大多数情况下,检测差异是很棘手的。在这些情况和由于平衡而导致的合法租约丢失之间,我们希望将这些情况的控制权委托给开发人员。

more on Event Hubs...

在我的例子中,我有一个本地实例 Function App 运行 在我的笔记本电脑上,另一个在云中。两者都具有相同的触发器配置(相同的 EH,相同的消费者)。