CategoryResourceRepost/极客时间专栏/设计模式之美/开源与项目实战:项目实战/94 | 项目实战二:设计实现一个通用的接口幂等框架(设计).md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

70 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="94 | 项目实战二:设计实现一个通用的接口幂等框架(设计)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ef/0e/ef2f5262282c85c0ec68b388ec07160e.mp3"></audio>
上一节课,我们介绍了幂等框架的一个重要需求场景,接口超时重试。为了避免同一业务被多次重复执行,接口需要支持幂等特性。同时,我们还对功能性需求和非功能性需求做了梳理。今天,我们来讲解幂等框架的设计思路。
跟限流框架类似,幂等框架的功能性需求也比较简单,但要考虑处理的异常情况有很多,比如业务代码异常、业务系统宕机、幂等框架异常。今天,我们重点讲解如何应对这些异常情况,设计一个高度容错的幂等框架。
话不多说,让我们正式开始今天的学习吧!
## 幂等处理正常流程
调用方从发起接口请求到接收到响应,一般要经过三个阶段。第一个阶段是调用方发送请求并被实现方接收,第二个阶段是执行接口对应的业务逻辑,第三个阶段是将执行结果返回给调用方。为了实现接口幂等,我们需要将幂等相关的逻辑,添加在这三个阶段中。
正常情况下幂等号随着请求传递到接口实现方之后接口实现方将幂等号解析出来传递给幂等框架。幂等框架先去数据库比如Redis中查找这个幂等号是否已经存在。如果存在说明业务逻辑已经或者正在执行就不要重复执行了。如果幂等号不存在就将幂等号存储在数据库中然后再执行相应的业务逻辑。
正常情况下,幂等处理流程是非常简单的,难点在于如何应对异常情况。在这三个阶段中,如果第一个阶段出现异常,比如发送请求失败或者超时,幂等号还没有记录下来,重试请求会被执行,符合我们的预期。如果第三个阶段出现异常,业务逻辑执行完成了,只是在发送结果给调用方的时候,失败或者超时了,这个时候,幂等号已经记录下来,重试请求不会被执行,也符合我们的预期。也就是说,第一、第三阶段出现异常,上述的幂等处理逻辑都可以正确应对。
但是,如果第二个阶段业务执行的过程出现异常,处理起来就复杂多了。接下来,我们就看下幂等框架该如何应对这一阶段的各种异常。我分了三类异常来讲解,它们分别是业务代码异常、业务系统宕机、幂等框架异常。
## 业务代码异常处理
当业务代码在执行过程中抛出异常的时候,我们是否应该认定为业务处理失败,然后将已经记录的幂等号删除,允许重新执行业务逻辑呢?
对于这个问题我们要分业务异常和系统异常来区分对待。那什么是业务异常什么是系统异常呢我举个例子解释一下。比如A用户发送消息给B用户但是查询B用户不存在抛出UserNotExisting异常我们把这种业务上不符合预期叫做业务异常。因为数据库挂掉了业务代码访问数据库时就会报告数据库异常我们把这种非业务层面的、系统级的异常叫做系统异常。
遇到业务异常比如UserNotExisting异常我们不删除已经记录的幂等号不允许重新执行同样的业务逻辑因为再次重新执行也是徒劳的还是会报告异常。相反遇到系统异常比如数据库访问异常我们将已经记录的幂等号删除允许重新执行这段业务逻辑。因为在系统级问题修复之后比如数据库恢复了重新执行之前失败的业务逻辑就有可能会成功。
实际上,为了让幂等框架尽可能的灵活,低侵入业务逻辑,发生异常(不管是业务异常还是系统异常),是否允许再重试执行业务逻辑,交给开发这块业务的工程师来决定是最合适的了,毕竟他最清楚针对每个异常该如何处理。而幂等框架本身不参与这个决定,它只需要提供删除幂等号的接口,由业务工程师来决定遇到异常的时候,是否需要调用这个删除接口,删除已经记录的幂等号。
## 业务系统宕机处理
刚刚分析的是代码异常,我们再来看下,如果在业务处理的过程中,业务系统宕机了(你可以简单理解为部署了业务系统的机器宕机了),幂等框架是否还能正确工作呢?
如果幂等号已经记录下了,但是因为机器宕机,业务还没来得及执行,按照刚刚的幂等框架的处理流程,即便机器重启,业务也不会再被触发执行了,这个时候该怎么办呢?除此之外,如果记录幂等号成功了,但是在捕获到系统异常之后,要删除幂等号之前,机器宕机了,这个时候又该怎么办?
如果希望幂等号的记录和业务的执行完全一致,我们就要把它们放到一个事务中。执行成功,必然会记录幂等号;执行失败,幂等号记录也会被自动回滚。因为幂等框架和业务系统各自使用独立的数据库来记录数据,所以,这里涉及的事务属于分布式事务。如果为了解决这个问题,引入分布式事务,那幂等框架的开发难度提高了很多,并且框架使用起来也复杂了很多,性能也会有所损失。
针对这个问题,我们还有另外一种解决方案。那就是,在存储业务数据的业务数据库( 比如MySQL建一张表来记录幂等号。幂等号先存储到业务数据库中然后再同步给幂等框架的Redis数据库。这样做的好处是我们不需要引入分布式事务框架直接利用业务数据库本身的事务属性保证业务数据和幂等号的写入操作要么都成功要么都失败。不过这个解决方案会导致幂等逻辑跟业务逻辑没有完全解耦不符合我们之前讲到的低侵入、松耦合的设计思想。
实际上做工程不是做理论。对于这种极少发生的异常在工程中我们能够做到在出错时能及时发现问题、能够根据记录的信息人工修复就可以了。虽然看起来解决方案不优雅不够智能不够自动化但是这比编写一大坨复杂的代码逻辑来解决要好使得多。所以我们建议业务系统记录SQL的执行日志在日志中附加上幂等号。这样我们就能在机器宕机时根据日志来判断业务执行情况和幂等号的记录是否一致。
## 幂等框架异常处理
我们前面提到,限流框架本身的异常,不能导致接口响应异常。那对于幂等框架来说,是否也适用这条设计原则呢?
对于限流来说限流框架执行异常比如Redis访问超时或者访问失败我们可以触发服务降级让限流功能暂时不起作用接口还能正常执行。如果大量的限流接口调用异常在具有完善监控的情况下这些异常很快就会被运维发现并且修复所以短暂的限流失效也不会对业务系统产生太多影响。毕竟限流只是一个针对突发情况的保护机制平时并不起作用。如果偶尔的极个别的限流接口调用异常本不应该被放过的几个接口请求因为限流的暂时失效被放过了对于这种情况绝大部分业务场景都是可以接受的。毕竟限流不可能做到非常精确多放过一两个接口请求几乎没影响。
对于幂等来说,尽管它应对的也是超时重试等特殊场景,但是,如果本不应该重新执行的业务逻辑,因为幂等功能的暂时失效,被重复执行了,就会导致业务出错(比如,多次执行转账,钱多转了)。对于这种情况,绝大部分业务场景都是无法接受的。所以,在幂等逻辑执行异常时,我们选择让接口请求也失败,相应的业务逻辑就不会被重复执行了。毕竟接口请求失败(比如转钱没转成功),比业务执行出错(比如多转了钱),修复的成本要低很多。
## 重点回顾
好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
今天,我们讲解了幂等框架的设计思路。在正常情况下,幂等框架的处理流程是比较简单的,调用方生成幂等号,传递给实现方,实现方记录幂等号或者用幂等号判重。但是,幂等框架要处理的异常情况很多,这也是设计的复杂之处和难点之处。
我们针对三种不同类型的异常,讲解了幂等框架的应对思路。
对于业务代码异常,为了让幂等框架尽可能的灵活,低侵入业务逻辑,发生异常(不管是业务异常还是系统异常),是否允许再重试执行业务逻辑,交给开发这块业务的工程师来决定。
对于业务系统宕机对于这种极少发生的异常在工程中我们能够做到在出错时能及时发现问题、能够根据记录的信息人工修复就可以了。所以我们建议业务系统记录SQL的执行日志在日志中附加上幂等号。这样我们就能在机器宕机时根据日志来判断业务执行情况和幂等号的记录是否一致。
对于幂等框架异常,跟限流框架异常处理对策不同,在幂等逻辑执行异常时,我们选择让接口请求也失败,相应的业务逻辑就不会被重复执行了,业务就不会出错。毕竟接口请求失败,比业务执行出错,修复的成本要低很多。
虽然幂等框架要处理的异常很多,但考虑到开发成本以及简单易用性,我们对某些异常的处理在工程上做了妥协,交由业务系统或者人工介入处理。这样就大大简化了幂等框架开发的复杂度和难度。
## 课堂讨论
我常说,异常情况考虑是否全面,处理是否得当,很能体现一个程序员的逻辑思维能力、工程能力。除了我们今天讲到的异常,在幂等框架中,你还能想到有哪些其他异常情况会发生?又该如何应对呢?
欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。