是什么导致了 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 topic
为 Kafka Consumers
所做的非常相似 - 分区所有权和检查点存储)由 Microsoft Azure EventHubs
团队自己编写 - 翻译所有 EventHubs partition receiver Gu
变成一个简单的 onReceive(Events)
回调。
EPH
用于解决 2 个一般的、主要的、well-known 问题,同时读出 high-throughput 分区流,如 EventHubs
:
容错接收 pipe-line - 例如:问题的更简单版本 - 如果主机 运行ning PartitionReceiver
死了又回来了——它需要从它离开的地方恢复处理。为了记住最后一次成功处理的 EventData
,EPH
使用提供给 EPH
构造函数的 blob
来存储检查点 - 当用户调用 context.CheckpointAsync()
时。最终,当主机进程终止时(例如:突然重启或遇到硬件故障和 never/comesback)- 任何 EPH
实例都可以接手此任务并从 Checkpoint
恢复。
Balance/distribute 个跨 EPH
个实例的分区 - 比方说,如果有 10 个分区和 2 个 EPH
个实例处理来自这 10 个分区的事件——我们需要一种方法来跨实例划分分区(EPH
库的 PartitionManager
组件执行此操作)。我们使用 Azure Storage - Blob LeaseManagement-feature
来实现。从版本 2.2.10
开始 - 为了简化问题,EPH
假定 所有分区均等加载 。
有了这个,让我们试着看看发生了什么:
因此,首先,在上面的 10
事件中心分区和 2
EPH
实例处理事件的示例中:
- 假设第一个
EPH
实例 - EPH1
启动,at-first,单独和 start-up 的一部分,它为所有 10 个分区创建接收器并正在处理事件。在启动时 - EPH1
将通过获取代表这些 10
事件中心分区的 10
存储 blob 的租约来宣布它拥有所有这些 10
分区(使用标准 nomenclature
- EPH
在存储帐户中内部创建 - 从 StorageConnectionString
传递到 ctor
)。租约将为 acquired for a set time,之后 EPH
实例将失去对该分区的所有权。
EPH1
不断 announces
偶尔 - 它仍然拥有这些分区 - 通过 renewing
租用 blob。可以使用 PartitionManagerOptions
执行 renewal
的频率以及其他有用的调整
- 现在,比方说,
EPH2
启动了 - 您还向 EPH2
的 ctor
提供了与 EPH1
相同的 AzureStorageAccount
。现在,它有 0
个分区要处理。因此,为了在 EPH
个实例之间实现分区平衡,它将继续 download
所有 leaseblobs
的列表,其中具有 owner
到 partitionId
的映射.由此,它将 STEAL
租赁 以公平分享 partitions
- 在我们的示例中为 5
,并将公布有关该信息lease blob
。作为其中的一部分,EPH2
读取由 PartitionX
写入的最新检查点,它想要窃取租约并继续并创建相应的 PartitionReceiver
与 EPOCH
相同Checkpoint
中的那个。
- 因此,
EPH1
将失去这 5 个 partitions
的所有权,并将 运行 根据其所处的确切状态陷入不同的错误。
- 如果
EPH1
实际上正在调用 PartitionReceiver.Receive()
调用 - 而 EPH2
在同一个接收器上创建 PartitionReceiver
- EPH1
将经历 ReceiverDisconnectedException。这最终将调用 IEventProcessor.Close(CloseReason=LeaseLost)
。请注意,如果接收到的消息更大或 PrefetchCount
更小,则命中此特定异常的可能性更高 - 因为在这两种情况下,接收方将执行更积极的 I/O.
- 如果
EPH1
处于checkpointing
lease
或renewing
lease
的状态,而EPH2
stole
租约时,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
个实例之间移动。
基本上,在大多数情况下,检测差异是很棘手的。在这些情况和由于平衡而导致的合法租约丢失之间,我们希望将这些情况的控制权委托给开发人员。
在我的例子中,我有一个本地实例 Function App 运行 在我的笔记本电脑上,另一个在云中。两者都具有相同的触发器配置(相同的 EH,相同的消费者)。
我正在使用 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 topic
为 Kafka Consumers
所做的非常相似 - 分区所有权和检查点存储)由 Microsoft Azure EventHubs
团队自己编写 - 翻译所有 EventHubs partition receiver Gu
变成一个简单的 onReceive(Events)
回调。
EPH
用于解决 2 个一般的、主要的、well-known 问题,同时读出 high-throughput 分区流,如 EventHubs
:
容错接收 pipe-line - 例如:问题的更简单版本 - 如果主机 运行ning
PartitionReceiver
死了又回来了——它需要从它离开的地方恢复处理。为了记住最后一次成功处理的EventData
,EPH
使用提供给EPH
构造函数的blob
来存储检查点 - 当用户调用context.CheckpointAsync()
时。最终,当主机进程终止时(例如:突然重启或遇到硬件故障和 never/comesback)- 任何EPH
实例都可以接手此任务并从Checkpoint
恢复。Balance/distribute 个跨
EPH
个实例的分区 - 比方说,如果有 10 个分区和 2 个EPH
个实例处理来自这 10 个分区的事件——我们需要一种方法来跨实例划分分区(EPH
库的PartitionManager
组件执行此操作)。我们使用Azure Storage - Blob LeaseManagement-feature
来实现。从版本2.2.10
开始 - 为了简化问题,EPH
假定 所有分区均等加载 。
有了这个,让我们试着看看发生了什么:
因此,首先,在上面的 10
事件中心分区和 2
EPH
实例处理事件的示例中:
- 假设第一个
EPH
实例 -EPH1
启动,at-first,单独和 start-up 的一部分,它为所有 10 个分区创建接收器并正在处理事件。在启动时 -EPH1
将通过获取代表这些10
事件中心分区的10
存储 blob 的租约来宣布它拥有所有这些10
分区(使用标准nomenclature
-EPH
在存储帐户中内部创建 - 从StorageConnectionString
传递到ctor
)。租约将为 acquired for a set time,之后EPH
实例将失去对该分区的所有权。 EPH1
不断announces
偶尔 - 它仍然拥有这些分区 - 通过renewing
租用 blob。可以使用PartitionManagerOptions
执行 - 现在,比方说,
EPH2
启动了 - 您还向EPH2
的ctor
提供了与EPH1
相同的AzureStorageAccount
。现在,它有0
个分区要处理。因此,为了在EPH
个实例之间实现分区平衡,它将继续download
所有leaseblobs
的列表,其中具有owner
到partitionId
的映射.由此,它将STEAL
租赁 以公平分享partitions
- 在我们的示例中为5
,并将公布有关该信息lease blob
。作为其中的一部分,EPH2
读取由PartitionX
写入的最新检查点,它想要窃取租约并继续并创建相应的PartitionReceiver
与EPOCH
相同Checkpoint
中的那个。 - 因此,
EPH1
将失去这 5 个partitions
的所有权,并将 运行 根据其所处的确切状态陷入不同的错误。- 如果
EPH1
实际上正在调用PartitionReceiver.Receive()
调用 - 而EPH2
在同一个接收器上创建PartitionReceiver
-EPH1
将经历 ReceiverDisconnectedException。这最终将调用IEventProcessor.Close(CloseReason=LeaseLost)
。请注意,如果接收到的消息更大或PrefetchCount
更小,则命中此特定异常的可能性更高 - 因为在这两种情况下,接收方将执行更积极的 I/O. - 如果
EPH1
处于checkpointing
lease
或renewing
lease
的状态,而EPH2
stole
租约时,EventProcessorOptions.ExceptionReceived
事件处理程序将用leaselostException
发出信号(在leaseblob
上有409
冲突错误)——这最终也会调用IEventProcess.Close(LeaseLost)
。
- 如果
renewal
的频率以及其他有用的调整
为什么租赁管理不能顺利 & 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
个实例之间移动。
基本上,在大多数情况下,检测差异是很棘手的。在这些情况和由于平衡而导致的合法租约丢失之间,我们希望将这些情况的控制权委托给开发人员。
在我的例子中,我有一个本地实例 Function App 运行 在我的笔记本电脑上,另一个在云中。两者都具有相同的触发器配置(相同的 EH,相同的消费者)。