CategoryResourceRepost/极客时间专栏/左耳听风/弹力设计/43 | 弹力设计篇之“异步通讯设计”.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

184 lines
13 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="43 | 弹力设计篇之“异步通讯设计”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7a/f9/7ae353e4ef2446b405b7fa4aa23604f9.mp3"></audio>
前面所说的隔离设计通常都需要对系统做解耦设计,而把一个单体系统解耦,不单单是把业务功能拆分出来,正如上面所说,拆分完后还会面对很多的问题。其中一个重要的问题就是这些系统间的通讯。
通讯一般来说分同步和异步两种。同步通讯就像打电话,需要实时响应,而异步通讯就像发邮件,不需要马上回复。各有千秋,我们很难说谁比谁好。但是在面对超高吐吞量的场景下,异步处理就比同步处理有比较大的优势了,这就好像一个人不可能同时接打很多电话,但是他可以同时接收很多的电子邮件一样。
同步调用虽然让系统间只耦合于接口,而且实时性也会比异步调用要高,但是我们也需要知道同步调用会带来如下几个问题。
<li>
同步调用需要被调用方的吞吐不低于调用方的吞吐。否则会导致被调用方因为性能不足而拖死调用方。换句话说,整个同步调用链的性能会由最慢的那个服务所决定。
</li>
<li>
同步调用会导致调用方一直在等待被调用方完成如果一层接一层地同步调用下去所有的参与方会有相同的等待时间。这会非常消耗调用方的资源。因为调用方需要保存现场Context等待远端返回所以对于并发比较高的场景来说这样的等待可能会极度消耗资源。
</li>
<li>
同步调用只能是一对一的,很难做到一对多。
</li>
<li>
同步调用最不好的是,如果被调用方有问题,那么其调用方就会跟着出问题,于是会出现多米诺骨牌效应,故障一下就蔓延开来。
</li>
所以,异步通讯相对于同步通讯来说,除了可以增加系统的吞吐量之外,最大的一个好处是其可以让服务间的解耦更为彻底,系统的调用方和被调用方可以按照自己的速率而不是步调一致,从而可以更好地保护系统,让系统更有弹力。
异步通讯通常来说有三种方式。
# 异步通讯的三种方式
## 请求响应式
在这种情况下发送方sender会直接请求接收方receiver被请求方接收到请求后直接返回——收到请求正在处理。
对于返回结果有两种方法一种是发送方时不时地去轮询一下问一下干没干完。另一种方式是发送方注册一个回调方法也就是接收方处理完后回调请求方。这种架构模型在以前的网上支付中比较常见页面先从商家跳转到支付宝或银行商家会把回调的URL传给支付页面支付完后再跳转回商家的URL。
很明显,这种情况下还是有一定耦合的。是发送方依赖于接收方,并且要把自己的回调发送给接收方,处理完后回调。
## 通过订阅的方式
这种情况下接收方receiver会来订阅发送方sender的消息发送方会把相关的消息或数据放到接收方所订阅的队列中而接收方会从队列中获取数据。
这种方式下发送方并不关心订阅方的处理结果它只是告诉订阅方有事要干收完消息后给个ACK就好了你干成啥样我不关心。这个方式常用于像MVCModel-View-Control这样的设计模式下如下图所示。
<img src="https://static001.geekbang.org/resource/image/d8/37/d8d96ed4e4616626b9e079dc13637937.png" alt="" />
这就好像下订单的时候,一旦用户支付完成了,就需要把这个事件通知给订单处理以及物流,订单处理变更状态,物流服务需要从仓库服务分配相应的库存并准备配送,后续这些处理的结果无需告诉支付服务。
为什么要做成这样?好了,重点来了!前面那种请求响应的方式就像函数调用一样,这种方式有数据有状态的往来(也就是说需要有请求数据、返回数据,服务里面还可能需要保存调用的状态),所以服务是有状态的。如果我们把服务的状态给去掉(通过第三方的状态服务来保证),那么服务间的依赖就只有事件了。
你知道分布式系统的服务设计是需要向无状态服务Stateless努力的这其中有太多的好处无状态意味着你可以非常方便地运维。所以事件通讯成为了异步通讯中最重要的一个设计模式。
就上面支付的那个例子商家这边只需要订阅一个支付完成的事件这个事件带一个订单号而不需要让支付方知道自己的回调URL这样的异步是不是更干净一些
但是,在这种方式下,接收方需要向发送方订阅事件,所以是接收方依赖于发送方。这种方式还是有一定的耦合。
## 通过Broker的方式
所谓Broker就是一个中间人发送方sender和接收方receiver都互相看不到对方它们看得到的是一个Broker发送方向Broker发送消息接收方向Broker订阅消息。如下图所示。
<img src="https://static001.geekbang.org/resource/image/aa/17/aa1c6db18e706012e8028b4d1bddb917.png" alt="" />
这是完全的解耦。所有的服务都不需要相互依赖而是依赖于一个中间件Broker。这个Broker是一个像数据总线一样的东西所有的服务要接收数据和发送数据都发到这个总线上这个总线就像协议一样让服务间的通讯变得标准和可控。
在Broker这种模式下发送方的服务和接收方的服务最大程度地解耦。但是所有人都依赖于一个总线所以这个总线就需要有如下的特性
- 必须是高可用的,因为它成了整个系统的关键;
- 必须是高性能而且是可以水平扩展的;
- 必须是可以持久化不丢数据的。
要做到这三条还是比较难的。当然好在现在开源软件或云平台上Broker的软件是非常成熟的所以节省了我们很多的精力。
# 事件驱动设计
上述的第二种和第三种方式就是比较著名的事件驱动架构EDA Event Driven Architecture。正如前面所说事件驱动最好是使用Broker方式服务间通过交换消息来完成交流和整个流程的驱动。
如下图所示,这是一个订单处理流程。下单服务通知订单服务有订单要处理,而订单服务生成订单后发出通知,库存服务和支付服务得到通知后,一边是占住库存,另一边是让用户支付,等待用户支付完成后通知配送服务进行商品配送。
<img src="https://static001.geekbang.org/resource/image/aa/59/aa95556d053e22be38a8beb40cf28759.png" alt="" />
每个服务都是“自包含”的。所谓“自包含”也就是没有和别人产生依赖。而要把整个流程给串联起来我们需要一系列的“消息通道Channel”。各个服务做完自己的事后发出相应的事件而又有一些服务在订阅着某些事件来联动。
事件驱动方式的好处至少有五个。
<li>
服务间的依赖没有了,服务间是平等的,每个服务都是高度可重用并可被替换的。
</li>
<li>
服务的开发、测试、运维,以及故障处理都是高度隔离的。
</li>
<li>
服务间通过事件关联所以服务间是不会相互block的。
</li>
<li>
在服务间增加一些Adapter如日志、认证、版本、限流、降级、熔断等相当容易。
</li>
<li>
服务间的吞吐也被解开了,各个服务可以按照自己的处理速度处理。
</li>
我们知道任何设计都有好有不好的方式。事件驱动的架构也会有一些不好的地方。
<li>
业务流程不再那么明显和好管理。整个架构变得比较复杂。解决这个问题需要有一些可视化的工具来呈现整体业务流程。
</li>
<li>
事件可能会乱序。这会带来非常Bug的事。解决这个问题需要很好地管理一个状态机的控制。
</li>
<li>
事务处理变得复杂。需要使用两阶段提交来做强一致性,或是退缩到最终一致性。
</li>
# 异步通讯的设计重点
首先,我们需要知道,为什么要异步通讯。
<li>
异步通讯最重要的是解耦服务间的依赖。最佳解耦的方式是通过Broker的机制。
</li>
<li>
解耦的目的是让各个服务的隔离性更好,这样不会出现“一倒倒一片”的故障。
</li>
<li>
异步通讯的架构可以获得更大的吞吐量,而且各个服务间的性能不受干扰相对独立。
</li>
<li>
利用Broker或队列的方式还可以达到把抖动的吞吐量变成均匀的吞吐量这就是所谓的“削峰”这对后端系统是个不错的保护。
</li>
<li>
服务相对独立,在部署、扩容和运维上都可以做到独立不受其他服务的干扰。
</li>
但我们需要知道这样的方式带来的问题,所以在设计成异步通信的时候需要注意如下事宜。
<li>
用于异步通讯的中间件Broker成为了关键需要设计成高可用不丢消息的。另外因为是分布式的所以可能很难保证消息的顺序因此你的设计最好不依赖于消息的顺序。
</li>
<li>
异步通讯会导致业务处理流程不那么直观因为像接力一样所以在Broker上需要有相关的服务消息跟踪机制否则出现问题后不容易调试。
</li>
<li>
因为服务间只通过消息交互,所以业务状态最好由一个总控方来管理,这个总控方维护一个业务流程的状态变迁逻辑,以便系统发生故障后知道业务处理到了哪一步,从而可以在故障清除后继续处理。
这样的设计常见于银行的对账程序银行系统会有大量的外部系统通讯比如跨行的交易、跨企业的交易等等。所以为了保证整体数据的一致性或是避免漏处理及处理错的交易需要有对账系统这其实就是那个总控这也是为什么银行有的交易是T+1隔天结算就是因为要对个账确保数据是对的。
</li>
<li>
消息传递中可能有的业务逻辑会有像TCP协议那样的send和ACK机制。比如A服务发出一个消息之后开始等待处理方的ACK如果等不到的话就需要做重传。此时需要处理方有幂等的处理即同一件消息无论收到多少次都只处理一次。
</li>
# 小结
好了,我们来总结一下今天分享的主要内容。首先,同步调用有四个问题:影响吞吐量、消耗系统资源、只能一对一,以及有多米诺骨牌效应。于是,我们想用异步调用来避免该问题。
异步调用有三种方式:请求响应、直接订阅和中间人订阅。最后,我介绍了事件驱动设计的特点和异步通讯设计的重点。下篇文章中,我们讲述幂等性设计。希望对你有帮助。
也欢迎你分享一下你在分布式服务的设计中,哪些情况下使用异步通讯?是怎样设计的?又有哪些情况使用同步通讯?
文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
<li>弹力设计篇
<ul>
- [认识故障和弹力设计](https://time.geekbang.org/column/article/3912)
- [隔离设计Bulkheads](https://time.geekbang.org/column/article/3917)
- [异步通讯设计Asynchronous](https://time.geekbang.org/column/article/3926)
- [幂等性设计Idempotency](https://time.geekbang.org/column/article/4050)
- [服务的状态State](https://time.geekbang.org/column/article/4086)
- [补偿事务Compensating Transaction](https://time.geekbang.org/column/article/4087)
- [重试设计Retry](https://time.geekbang.org/column/article/4121)
- [熔断设计Circuit Breaker](https://time.geekbang.org/column/article/4241)
- [限流设计Throttle](https://time.geekbang.org/column/article/4245)
- [降级设计degradation](https://time.geekbang.org/column/article/4252)
- [弹力设计总结](https://time.geekbang.org/column/article/4253)
- [分布式锁Distributed Lock](https://time.geekbang.org/column/article/5175)
- [配置中心Configuration Management](https://time.geekbang.org/column/article/5819)
- [边车模式Sidecar](https://time.geekbang.org/column/article/5909)
- [服务网格Service Mesh](https://time.geekbang.org/column/article/5920)
- [网关模式Gateway](https://time.geekbang.org/column/article/6086)
- [部署升级策略](https://time.geekbang.org/column/article/6283)
- [缓存Cache](https://time.geekbang.org/column/article/6282)
- [异步处理Asynchronous](https://time.geekbang.org/column/article/7036)
- [数据库扩展](https://time.geekbang.org/column/article/7045)
- [秒杀Flash Sales](https://time.geekbang.org/column/article/7047)
- [边缘计算Edge Computing](https://time.geekbang.org/column/article/7086)