我认为,从来没有完全任何解决方案可以 100% 确保永远不会在两方之间丢失消息。即使您在两个服务之间有一个服务总线,也总是有机会(可能非常小,但绝不会为空)服务总线出现故障,或者与服务总线的通信出现故障。话虽如此,当然有些模型不太可能很少丢失消息,但是您无法完全绕过您仍然必须处理客户端中的错误的事实。
事实上,Service Fabric 故障处理主要是围绕客户端重试通信而设计的,而不是让服务或中介来做这件事。这有很多原因(我猜),但其中之一是分布式、复制、可靠服务的性质。如果主服务出现故障,副本将承担责任,但它不会知道主服务在它死亡的那一刻正在做什么(除非它复制了它的状态,但它可能在此之前就已经死亡)。在这种情况下,唯一真正知道自己想要做什么的是客户端。客户端知道它在做什么,并且可以对服务中的不同故障场景做出反应。在 Fabric Transport 中,大多数已知的异常可能“自然”发生,比如服务挂掉或者网线被看门人剪断,其实都是自动重试的。这包括重新解析地址,以防万一服务主要被替换为辅助服务。
引入第三个服务或服务总线的场景实际上也是如此。如果在消息完全到达服务之前网络出现故障怎么办?在这种情况下,只有客户端知道出了什么问题以及它打算发送什么。如果它在到达服务之后但在响应发送之前发生了故障怎么办?在这种情况下,客户端必须假设消息从未到达并尝试重新发送它。这也是为什么服务方法被推荐为幂等的——同一个客户端可以多次调用同一个调用。
即使您要引入辅助部分,如服务总线,仍然存在服务总线出现故障的风险,或者更有可能连接到服务总线的网络出现故障。因此,客户端需要重试,并且当它重试多次时,它所能做的就是将消息放入失败消息的队列中,或者只是将其记录下来,或者将异常抛出回原始调用者(在您的场景中,浏览器)。
好吧,那是我很悲观。但它可能会发生。以上所有的事情,只是有些不太可能发生。但它们可能会发生。关于你的问题:
1)使无状态服务有状态的问题是您现在必须在调用者中处理分区。您可以为有状态服务设置 Http 侦听器,但您必须在 Uri 中包含分区和副本信息,这不适用于负载均衡器,因此在这种情况下浏览器在调用 API 时必须选择分区。不是一个理想的解决方案。
2)是的,您可以这样做,即在为您排队消息之间引入其他内容。没有什么可以说服务总线或数据库比具有可靠队列的有状态服务更可靠,这取决于您选择最适合的方式。我会选择有状态服务,这样我就可以轻松地将所有内容保存在我的 SF 应用程序中。但同样,这并不是 100% 保护心怀不满的看门人用剪刀,因为您仍然需要可以处理故障的客户端。
3)确保您有办法处理错误(重试)并使用客户端(服务 1)记录或存储失败的消息(重试后)。
3.a)一种方法是让它本地存储在它正在运行的节点上,并定期(例如 RunAsync)尝试重新运行那些失败的消息。在运行它的节点完全被核破坏并丢失数据的情况下,这可能是危险的,但是该数据不会被复制。
3.b)另一个是使用 ETW 的语义日志记录,并在事件中包含足够的数据,以便能够从记录的消息中重新创建消息并构建一些功能,可能是手动 UI,您可以从中重新运行它记录的信息。就像您在服务总线中的错误队列上重试失败的消息一样。
3.c)将失败的消息存储到其他任何不会失败的东西(数据库、服务总线、队列),原因与与服务 2 的通信相同。
我在这里的主要观点是(我可能已经开始了)是有很多场景只有客户知道足以处理这种情况。因此,请确保您有处理客户故障的策略。