未知异常

1106 发布于: 2021-03-24 读完约需 3 分钟

当使用Elasticsearch.NetNEST客户端类库发起Elasticsearch远程调用并抛出不能被IConnection处理的异常时,此异常又会当作UnexpectedElasticsearchClientException类型的异常被冒泡式地向上抛出到客户端,不管客户端是否配置了抛出异常的选项。

一个IConnection需要知道哪些异常是可以恢复的,哪些是不能被恢复的,IConnection的默认设置如下:

  • 桌面端的CLR是基于WebRequest,它可以从WebException中恢复
  • .NET Core CLR是基于HttpClient,它可以从HttpRequestException中恢复

其他异常会导致连接立即退出管道,以下将使用虚拟集群测试框架来演示这一点:

var audit = new Auditor(() => VirtualClusterWith  // 设置10个节点的Elastisearch集群
    .Nodes(10)
    .ClientCalls(r => r.SucceedAlways())
    .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!"))) // 端口为9201上的节点始终会抛出异常
    .StaticConnectionPool()
    .Settings(s => s.DisablePing())
);

audit = await audit.TraceCall(
    new ClientCall {
        { AuditEvent.HealthyResponse, 9200 }, // 第一次调用9200端口将返回一个正常的响应
    }
);

audit = await audit.TraceUnexpectedException(
    new ClientCall {
        { AuditEvent.BadResponse, 9201 }, // 第二次调用9201端口将返回失败的响应
    },
    (e) =>
    {
        e.FailureReason.Should().Be(PipelineFailure.Unexpected);
        e.InnerException.Should().NotBeNull();
        e.InnerException.Message.Should().Be("boom!");
    }
);

有时,未知的异常会发生在连接管道的更深处。在这种情况下,将些未知的异常封装在一个UnexpectedElasticsearchClientException,这样就可以完整记录异常在管道中发生位置的信息,而不会丢失。

在接下来的示例中,调用9200端口失败并引发了WebException异常,然后该调用转到9201上重试,9201端口会抛出一个IConnection的异常。最终,可以断言我们仍然可以看到整个请求的异常审计跟踪。测试代码如下:

var audit = new Auditor(() => VirtualClusterWith
    .Nodes(10)
#if DOTNETCORE
    .ClientCalls(r => r.OnPort(9200).FailAlways(new System.Net.Http.HttpRequestException("recover"))) // 调用9200端口时抛出HttpRequestException或者WebException异常
#else
    .ClientCalls(r => r.OnPort(9200).FailAlways(new System.Net.WebException("recover"))) 
#endif
    .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!"))) // 调用9201端口时抛出Exception异常
    .StaticConnectionPool()
    .Settings(s => s.DisablePing())
);

audit = await audit.TraceUnexpectedException(
    new ClientCall {
        { AuditEvent.BadResponse, 9200 },
        { AuditEvent.BadResponse, 9201 }, // 断言客户端调用的审计跟踪包括来自9200和9201的错误响应
    },
    (e) =>
    {
        e.FailureReason.Should().Be(PipelineFailure.Unexpected);
        e.InnerException.Should().NotBeNull();
        e.InnerException.Message.Should().Be("boom!");
    }
);

如果在使用Ping或者Sniff时出现了未知的异常,Elasticsearch.Net或者NEST客户端将试图从中恢复,并将故障转移到下一个节点上重新尝试。

以下示例中,启用了在第一次使用时ping节点,端口9200上的节点在ping时抛出了异常,此时,将故障转移到端口9201上的节点上重试ping,此节点ping成功了。之后,对9201的客户端调用时抛出一个无法恢复的异常,代码如下:

var audit = new Auditor(() => VirtualClusterWith
    .Nodes(10)
    .Ping(r => r.OnPort(9200).FailAlways(new Exception("ping exception")))
    .Ping(r => r.OnPort(9201).SucceedAlways())
    .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!")))
    .StaticConnectionPool()
    .AllDefaults()
);

audit = await audit.TraceUnexpectedException(
    new ClientCall {
        { AuditEvent.PingFailure, 9200 },
        { AuditEvent.PingSuccess, 9201 },
        { AuditEvent.BadResponse, 9201 },
    },
    e =>
    {
        e.FailureReason.Should().Be(PipelineFailure.Unexpected);

        e.InnerException.Should().NotBeNull();
        e.InnerException.Message.Should().Be("boom!"); // InnerException 是导致请求中断的异常

        e.SeenExceptions.Should().NotBeEmpty(); // 但是发生在Ping上的异常仍然可用
        var pipelineException = e.SeenExceptions.First();
        pipelineException.FailureReason.Should().Be(PipelineFailure.PingFailure);
        pipelineException.InnerException.Message.Should().Be("ping exception");

        var pingException = e.AuditTrail.First(a => a.Event == AuditEvent.PingFailure).Exception; // 异常可能很难关联到某个时间点,因此可以在异常的审计跟踪中找到异常
        pingException.Should().NotBeNull();
        pingException.Message.Should().Be("ping exception");
    }
);

版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。

发表评论

登录用户才能发表评论, 请 登 录 或者 注册