如何响应服务故障
当你的某个依赖服务出现故障时,你应当如何响应?作为一个服务的开发人员,你对依赖服务故障的响应必须是:
可预测的
可理解的
对当前情形是合理的
我们来分别讨论一下其中的每一点。
可预测的响应
拥有可预测的响应,是当前服务能够依赖其他服务的一个重要方面。你必须对一系列特定的环境和请求提供可预测的响应,才能够避免之前提到的级联故障。否则,如果你掉以轻心,那么即使一次很小的故障也可能导致级联性的大面积故障,并最终引发大问题。
因此,即使其中一个下游的依赖服务出现了故障,你仍然需要为其生成一个可预测的响应,例如,可能是一个错误提示消息。只要在你的API中包含了适当的错误处理机制,生成这种错误响应是完全可以接受的。
错误响应与不可预测的响应并不一样。一个不可预测的响应指的是服务预料之外的响应,而错误响应是一个有效的响应,表示你无法处理特定的请求。二者是不同的。
如果你的服务正要执行操作“3+5”,那么它应该返回一个数字,准确地说应该是数字“8”,这是一个可预测的响应。如果你的服务准备执行操作“5/0”,那么它应该返回“非数字”或者“错误,无效请求”。这些都属于可预测的响应。不可预测的响应指的是例如某一次返回“50000000000”,但是下一次又返回“38393384384337”的情况(有时候又被称为“无用的输入输出”)。
一个属于“无用的输入输出”的响应不是一个可预测的响应,相对而言,一个可预测的响应应该是“无效的请求”。
你的上游依赖服务会希望你提供一个可预测的响应。当你遇到无用输入时,不要也返回一个无用输出。如果你将一个不可预测的响应提供给了下游服务,那么会在整个服务调用链上传递这种不可预测性。这种不可预测的行为迟早会反馈到你的用户那里,从而影响业务的发展。可能更糟的是,这种不可预测的响应会将无效的数据插入你的业务流程,导致业务流程数据不一致和数据无效。这会影响你的业务分析,也会给用户造成不好的体验。
即使你的依赖服务出现故障或者发生了不可预测的行为,你也应尽可能不要把这种不可预测性传递给依赖于你的服务。
可预测的响应实际上意味着它是一个计划中的响应。不要有“好吧,如果依赖服务出现故障,我也做不了任何事情,只好任由服务出现故障”这样的想法。如果所有事情都出现故障了,你需要主动发现在当前情况下,一个合理的响应应当是什么样的,然后再检测当前情况是否满足条件,并返回预期的响应。
可理解的响应
可理解,意味着你和上游服务之间对响应的格式和结构都表示同意,从而在你和上游服务之间形成了一个约定。即使你的依赖服务出现了故障,你的响应也必须控制在约定的边界之内。永远不应该仅仅因为依赖服务违反了它的API约定,你就违反你自己的API约定。应该确保约定的接口能够覆盖所有意料之外的情况,包括依赖服务出故障的情况。
合理的响应
你的响应应该说明当前服务实际发生了什么事情。当问到“3+5等于几”的时候,即使依赖服务出现故障,也不应该返回内容为“红色”的响应。你的服务可以返回“对不起,我无法计算结果”或者“请稍后尝试”的响应,但绝对不应该返回“红色”作为答案。
虽然这听上去没什么,但是你会对实际中不合理的响应所带来的问题数量感到惊讶。例如,假设一个服务希望获取一个已过期、需要删除的账户列表,如图5-4所示。你可能会调用一个“过期账户”服务(返回一个需要删除的账户列表),然后继续删除列表中的所有账户。
图5-4:不合理的API响应
如果“过期账户”服务出现问题,无法返回有效的响应,它应当返回“无”,或者“对不起,现在无法返回结果”这样的响应。想象一下,如果它没有返回一个合理的响应,而是返回了系统中所有账户的列表呢?在这种情况下,“管理服务”会继续尝试并删除系统中的所有账户,这肯定是错误的做法,如果突然删除应用程序中的所有账户,结果将是灾难性的。