通过有效的错误管理提高系统的鲁棒性
通常,系统的鲁棒性来自全面有效的错误管理。由于在我们的软硬件系统环境中,任何一个部分都可能发生错误,因此我们需要以不同的方式予以处理。例如:
数据中心——整个数据中心(DC)可能由于电源故障、网络连接故障、环境灾难等,而变得不可用。硬件设备——服务器、存储部件可能出现硬盘故障、磁盘写满、可分配的资源耗尽、以及其他硬件错误等问题。软件应用——无论应用程序的技术堆栈如何,都可能出现应用报错、软件行为异常、以及程序级别的缺陷等。
为了应对上述来自各个方面的故障,我们往往需要通过如下手段,来提供系统的自愈能力:
通过监控,提供电源、网络、冷却系统、以及其他方面的冗余,来实现数据中心的高可用性。通过云端部署,来减少错误的实例,使用更加成熟的技术堆,基于微服务的分布式架构。监控服务器的各种参数,采用各种高可用性的部署模式,运用带有DevOps强大功能的容器化模式。通过应用各种可替代的架构与设计模式,来最小化错误。例如,用户请求的异步处理,可以有助于避免服务器过载的出现,并能够为用户提供一致性的体验。
可见,无论是系统架构师、还是应用设计人员,他们的主要目标都要根据实际业务需求和成本影响,精心考虑和设计各个组件的高可用性,并能够优雅地处理应用程序的错误。
模式的简要说明
目前,业界有许多种架构模式和方法,可以满足不同的应用架构范式、功能需求、NFR(Non-Failure
Request)、以及应用程序的故障恢复能力。例如:
如果应用是基于微服务的,那么我们的重点就应当放在微服务的集成依赖性的容错上。如果应用是基于事件的架构,那么除了正常的错误处理之外,我们还应该注意处理幂等性、以及在出现问题时可能造成的数据丢失上。基于API同步的应用程序,虽然可以便捷地将错误返回给调用者,但是如果问题持续更长的时间,我们则需要更加实用的监控、以及事件管理机制。在基于批处理的组件中,我们可能应该将重点放在以幂等的方式,重新启动或恢复原有的批处理能力上。错误代码
如果没有关于错误代码的通用约定与指南,每个应用或系统将会按照自定义的默认错误代码方式,根据用例和设计自行处理。而这有可能会导致不同方式相互之间的冲突。可见,在应用程序的错误处理过程中,我们该事先定义好错误代码,通过标准化且直观的错误处理方式,既提高解决问题的效率,又能够通过离线分析的方式,统计错误数量、负载峰值、以及特定类型故障的影响等细节。
错误处理
下面的示意图展示了如何在基于事件的应用程序中,处理各种错误。当然,其中具体涉及到的步骤,可能会因架构模式的不同而有所差异。
首先,我们应当区分应用程序的可重试(retryable)错误和不可重试(non-retryable)错误。例如,当输入的消息本身存在问题时,通常除非得到人工干预,否则重试此类错误是没有意义的。而那些数据库连接方面的问题,是值得进行重试的。
当应用程序出现重试类型的错误时,我们可以选择统一的“错误重试配置”方式,来进行微调处理。如下表所示,在基于事件的服务中,一旦基础设施组件出现可用性的缺失,我们需要通过预定义的反复重试机制,来及时确认运营商是否已及时修复。这往往比直接怀疑和处置由并发量请求所引发的问题可能性,要更加符合常理。
触发事件
在所有重试都以失败告终时,我们需要有一种方法,来触发事件并升级错误。在简单情况下,我们可以将问题的相关信息,直接以通知的形式,反馈给用户,并且建议其重新提交所需的请求。但是有些问题源于某个内部技术问题,所引发并导致的用户体验度的骤降。例如,在基于事件的架构中,异步集成模式通常使用DLQ(译者注:Dead
Letter
Queue,死信队列)作为错误处理模式。不过,DLQ只是整个过程中的一个临时步骤。我们仍然需要通过触发事件或发送警报的方式,去可靠地升级错误。那么,我们该如何设计一个事件与警报相集成的管理系统呢?下面,我们将讨论两种主要的方法:
第一种方法:当应用程序完成了所有重试之后,我们需要利用其可用的日志功能,构建可靠的错误报告路径,以减少丢失出错信息的可能。虽然业界已有成熟的日志记录标准。但是,我们仍然需要将各个错误日志区别开来,以免事件管理系统中充满了不相关的错误信息。我们通常将此类日志称为“错误警报”。它们往往是由专用的代码库和组件,按照预先设定的格式,及时产生大量的错误信息。下面是一段代码示例:
Java
{
"logType": "ErrorAlert",
"errorCode": "subA.compA.DB.DatabaseA.Access_Error",
"businessObjectId": "234323",
"businessObjectName": "ACCOUNT",
"InputDetails" : "",
"InputContext" : " any context info with the input",
"datetime": "date time of the error",
"errorDetails" : "Error trace",
"..other info as needed": "..."