《Akka应用模式:分布式应用程序设计实践指南》读书笔记7

容错

  容错绝对是分布式系统最难搞定的事儿,至少我这样认为,因为意外总是会发生。

  处理故障在许多方面意味着要放弃全局一致性。Akka是基于不粗要调用方负责处理故障的想法而建立的。它主张由发生故障的actor负责处理问题,在actor不能处理的情况下,会向其“监督者”寻求帮助。由于Actor模型基于消息机制设计,就意味着系统的很多部分都是异步的,异步又进一步导致出现故障时解决问题的难度。而Akka有一套完整的机制,将故障尽量限制在对应的actor上,缩小出现故障的影响范围和降低解决故障的成本和时间。但·Akka只是提供了故障恢复的机制,具体怎么恢复还是需要我们自己设计。

故障类型

  在之前我也说过,分布式系统有很多不确定性,会出现各种意外,对意外分门别类总是有好处的。

异常

  不管是单机系统,还是分布式系统,系统故障中最常见的类型之一。其实自从学了Scala,觉得传统的异常机制不太完美。因为它以一种奇怪的方式改变了程序的流程。而且绝大部分你总是会发现没有处理的异常,也就是说,异常变成了程序异常退出的一个出口,而异常一旦有很多个,就意味着程序“千疮百孔”,各种漏洞。这样的结果很可能导致系统释放资源的代码没有资源,造成资源泄露或其他意想不到的问题。所以我更喜欢用一个Try来封装所有的异常。

JVM致命错误

  这就比较难办了,很多时候都是任期发生,然后找出问题再解决。需要注意的是,Try无法处理这种会使系统崩溃的严重错误,比如内存溢出。看看Try的源码就知道为啥了。

外部服务故障

  系统往往需要与外部系统进行交互,外部API出现问题也是比较头疼的。因为问题不可控,我们只能选择接受,而无法解决。但意识到这种问题的存在非常重要。

不符合服务等级协议(SLA)

  其实这就比较主观了,毕竟SLA还是可以定的比较宽松的,^_^。如果一个系统总是能返回一个正确的结果,但却不一定能保证在合理的时间内返回,这就比较尴尬了。最好的系统仍然可能成为高负载的情况下的受害者,因为它无法达到预定义的服务等级协议(SLA)。这其实还挺尴尬的,有时候SLA还会变化,就更麻烦了。

操作系统和硬件级故障

  这个吧,只能买好一点的机器。。。不过还是可以指定适当的预防措施的。

故障隔离

  我觉得这是处理故障的最正确的一个设计理念。那就是不要试图去消除全部的故障,而是承认其存在,将其限定在合理的范围内,隔离其影响范围。幸运的是,Akka提供了相关的工具。

舱壁模式

  这种模式对于我来说还是比较新颖的,因为我一直以为船舶一旦触礁,就会有大量的水流到船体内,然后就挂了。Akka提供了以actor层次结构形式实现的舱壁模式工具。也就是actor内部的错误不会蔓延,只影响其自身的功能。如果需要,还可以进一步分解,让特定actor只处理特定时间段内的请求,这样可以将请求中的故障隔离在请求发生的时间段内。

  这种设计理念我还是很喜欢的,毕竟很多时候,导致出现故障的原因,也许只是遇到了某个异常的请求,忽略这个请求,简单的重启继续后续消息的处理也许是非常合理的。

优雅降级

  这在互联网的架构博客中经常会被提及。其实刚开始我对它的理解比较粗浅,什么优雅降级,不就是let it go吗?所谓的优雅降级,就是牺牲一部分系统功能,使当前最重要的业务功能可用。优雅降级可以避免使这鞥个站点发生故障,只让其中一部分不可用,其他功能不受故障影响。这种降级,有时候是系统自动发生的,有时候是人工启动的。比如在业务高峰期,系统资源不足,可以选择性的将部分功能下线,以确保最核心的业务功能可用,就是所谓的丢车保帅。

使用Akka集群隔离故障

  Akka内置了舱壁模式,使其更加透明。集群分片某种意义上来说,就是对actor进行故障隔离。另外一个好处就是它可以自动将失败的节点上的actor重新分配给其他节点。

使用熔断器控制故障

  熔断器通常用来解决高负载情况下系统的故障隔离的。简单点来说,就是当系统处于高负载时,为了避免后续的消息使情况进一步恶化,成为压死骆驼的最后一根稻草,自动的使所有调用失败,甚至不会尝试处理调用请求。也就是尽快的通知调用方,调用失败了。故障恢复或者负载变低时,调用恢复正常。熔断器提供 一个让发生故障的系统在一段时间内恢复原有功能的方法。

  在预先设置好的时间段过去之后,熔断器进入“半开”状态。此时,进入熔断器的第一个调用将被处理,如果调用成功,则熔断器进入关闭状态,系统恢复正常运行。如果第一个调用还是失败,则熔断器返回到“打开”状态,继续使所有的调用快速失败。如果回到“打开”状态,则在接下来的一段时间内都保持打开状态。

  幸运的是Akka内置了熔断器CircuitBreader,使用还是比较方便的。

 故障处理

  前面已经对故障的类型做了分类,也对故障隔离和故障控制做了介绍,但没有说如何解决故障。Akka是设计理念是“let it crash”。从崩溃中恢复才是弹性系统应该具备的能力。首先认识的故障的发生,才能正面的解决和规避它。

异常处理

  异常一般分为非致命异常和致命异常。当然了是否致命是对整个系统来说的,不是对某个代码、函数或者actor来说的。

  在单个actor中,可以简单的处理可预测的故障,比如用try catch快封装;当异常不能被它的actor处理时,就会把异常发送给上层的监督者,由监督者决定如何处理。幸运的是Akka内置了几种常见的恢复策略。OneForOneStrategy提示监督者将恢复逻辑应用于发生故障的actor,其他actor不受影响;AllForOneStrategy告诉监督者将恢复逻辑应用于所有的子actor。恢复逻辑一般就是重启,actor重启的代价比较小,只需要做好相关的业务初始化代码即可。Decider是一个partial函数,它根据异常类型决定采取的应对方式。Decider将异常类型映射到以下执行。

  Resume。告诉发生故障的actor继续处理消息,即忽略发生故障的消息。

  Restart。告诉发生故障的actor应该重启。

  Stop。告诉发生故障的actor应该被停止。比如临时、一次性的actor,失败后不再需要,就可以停止。

  Escalate。将故障升级。其实就是把异常向上汇报,由监督者的监督者处理。

  其实选择合适的异常处理策略还是比较难的,因为异常就是意外,很难预测。我们所能做的就是对已知的故障进行应对,无法预见的就只能交给后期版本迭代了。

  JVM致命错误怎么办呢?Akka自身没有现成的工具可以做到这一点。但可以引入第三方工具,比如ConductR或者supervisor。当检测到对应JVM退出时,简单的重启。但还可以优化一下,比如在应用程序中设置一个回退点。在Akka中,我们可以选择将消息持久化,从某个时间点开始,回放消息。当然了这需要系统具备至少一次的交付机制。

猜你喜欢

转载自www.cnblogs.com/gabry/p/9173392.html