为什么我们不把异步消息/事件作为微服务架构中唯一的通信模式,并删除所有的sync/http调用呢?
我无法想象同步在任何情况下都比异步更有优势。
想象一下3个服务A、B、C之间的以下呼叫链:
UI->A->B->C
-
UI调用a请求一些数据。
-
A需要调用B来获取一些数据,在返回UI之前,它将对这些数据进行一些处理。
-
同样,B需要调用C来获取一些数据。
想象一下,所有服务都有(有限的)线程池来处理传入的请求。
A中已接受来自UI的连接请求的线程将向B发出HTTP请求。
由于对B的请求是IO的一种形式,因此该线程将被抢占,直到数据从a->B->C->B->A、 从而长时间浪费线程。
其他服务中也会发生类似的线程抢占。
如果我们有一个消息驱动的设计,我们可以在每个服务中拥有以下线程池:
-
一个线程池,它只是运行kafka消费者来读取下游服务的响应。
-
一个线程池,用于接受来自上游服务的新请求。
考虑以下事件顺序:
-
A: :Thread1从UI获取请求,并生成消息'
MsgAB
'将请求id与核心数据一起封装到消息队列中。
-
A: :Thread1将请求连接对象插入到以下对象的全局映射中
requestId -> requestObject
.
-
A: :Thread1现在可以免费为更多请求提供服务。
-
同样,B会为C生成msg。
-
C将创建一个响应消息供B读取,这反过来将产生
MsgBA
.
-
现在A::Thread2运行一个卡夫卡消费者将消费
MsgBA
封装原始请求id。
-
A: :Thread2将使用全局映射中的请求对象回复UI。
这种设计的优点是,线程是免费的,可以立即提高服务的吞吐量,除了消息驱动架构的通常好处,如可扩展性和可扩展性(消息也可以通过日志记录、跟踪、数据分析或任何其他服务读取,而生产服务甚至不知道)。
为了处理超时,我们可以采用以下策略:
-
我们可以将所有请求ID保存在任何时间敏感的数据结构中,这允许快速访问和删除最旧的条目。
堆或LRU缓存(使用标准DLL+hashmap实现)可能是这种数据结构的不错选择。
-
我们可以让一个专用线程运行一个调度器,该调度器遍历此数据结构,并对所有过期/超时的请求回复超时错误。
那么,为什么基于HTTP的微服务架构如此流行呢?