背景:什么是异常
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?
Java提供了更加优秀的解决办法:异常处理机制。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。
正文
大多数时候,我们对异常的处理主要为,打一行log,进入一个预设的降级流程。对于一个端到端的应用来说,异常造成的后果往往是一次访问失败,无关紧要。这里想讨论的则要特殊一些,当这种失败必须要进行重试时,语法层面的重试次数与时间开销往往是无法接受或者满足的。我们需要一个优雅的发现、识别并处理故障的策略,实现错误处理的解耦。即异步场景下,如何保护异常现场,以及如何清洗异常数据。
在我过去的半年的工作中,无论是内容同步、内容分发、内容质控、内容审核,它们本质上都是一个异步的事件驱动应用,对数据进行装饰、打标、曝光。这种有点像ETL的应用,事实上很容易出现一些失败,比如网络异常、处理超时、上下游系统崩溃等。这些失败的数据,有时候是很难挑出来的,扫一次几百万行的表开销很大,也无必要,另一方面,在一套系统中,原始数据也不尽然都能找到。可以看到,当需要对问题数据进行修复的时候,人工介入去寻找问题数据本身就很麻烦。日志是一个保护现场的手段,但是不够好用。如果选择存入数据库,则又有些过于麻烦。
OpenTSDB for Kafka给了我一个启发,为什么不把异常现场写入Kafka,再配备一套异常数据清洗逻辑呢?异常数据清晰逻辑可以自动调度,延时调度,也可以手动调度。而且,对于上述的这种数据应用而言,往往这个异常数据清洗逻辑只是一次简单的重试,并不需要额外的实现。
由于我有分发、同步、质控、审核等多个应用场景,我决定抽出一个“公共服务”来做这样的事。它将采集异常现场,持久化存储,配备调度能力,支持即时处理、延时处理、手动处理。它被我称之为Crow。Crow不是一个壮举,只是对“脏数据”的责任。后来我了解到,这有点像是消息队列中的“死信队列”。
依托于一种或多种通信框架之后,Crow的实现与接入成本十分有限,但是有效。通过AOP来获取异常信息与异常现场,对于应用而言几乎是透明的,大多情况下,接入成本只是为方法添加一个注解用以标记。
在这个基础上,出现了两个主要问题,一是,如何做业务隔离,二是,切断循环调用链路。这里的背景是,在MTDP,为每一个业务方去申请一个topic,流程上有些麻烦,也不利用管控,另一方面,为了复用原始代码进行重试,在不破坏原有接口与约定的基础上,需要进行Crow调用与正常业务调用以切断玄幻调用。第二个问题相对容易,在点评,RPC框架Pigeon可以透传链路字段,http也可以通过head来达到相似的效果。第一个问题的初步方案则是,在原本的topic之外额外增加一个慢速topic,用于自动管控、降级大量侵占资源的应用的消息。
网友评论