SpringCloud升级之路2020.0.x版-2.微服务框架

2021-08-02  本文已影响0人  干货满满张哈希
image

本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~

image image

上图中演示了一个非常简单的微服务架构:

在这个微服务架构中的每个进程需要实现的功能都在下图中:

image

接下来我们逐个分析这个架构中的每个角色涉及的功能、要考虑的问题以及我们这个系列使用的库。

image

每个微服务的基础功能包括:

另外还会有重试机制,限流机制以及断路机制,这里我们先来关心最核心的针对调用其他微服务的 Http 客户端中的这些机制以及需要考虑的问题。

image

来看几个场景:

1.在线发布服务的时候,或者某个服务出现问题下线的时候,旧服务实例已经在注册中心下线并且实例已经关闭,但是其他微服务本地有服务实例缓存或者正在使用这个服务实例进行调用,这时候一般会因为无法建立 TCP 连接而抛出一个 java.io.IOException,不同框架使用的是这个异常的不同子异常,但是提示信息一般有 connect time out 或者 no route to host。这时候如果重试,并且重试的实例不是这个实例而是正常的实例,就能调用成功。如下图所示:

image

2.当调用一个微服务返回了非 2XX 的响应码

a) 4XX:在发布接口更新的时候,可能调用方和被调用方都需要发布。假设新的接口参数发生变化,没有兼容老的调用的时候,就会有异常,一般是参数错误,即返回 4XX 的响应码。例如新的调用方调用老的被调用方。针对这种情况,重试可以解决。但是为了保险,我们对于这种请求已经发出的,只重试 GET 方法(即查询方法,或者明确标注可以重试的非 GET 方法),对于非 GET 请求我们不重试。如下图所示:

image

b) 5XX:当某个实例发生异常的时候,例如连不上数据库,JVM Stop-the-world 等等,就会有 5XX 的异常。针对这种情况,重试也可以解决。同样为了保险,我们对于这种请求已经发出的,只重试 GET 方法(即查询方法,或者明确标注可以重试的非 GET 方法),对于非 GET 请求我们不重试。如下图所示:

image

3.断路器打开的异常:后面我们会知道,我们的断路器是针对微服务某个实例某个方法级别的,如果抛出了断路器打开的异常,请求其实并没有发出去,我们可以直接重试。

这些场景在线上在线发布更新的时候,以及流量突然到来导致某些实例出现问题的时候,还是很常见的。如果没有重试,用户会经常看到异常页面,影响用户体验。所以这些场景下的重试还是很必要的。对于重试,我们使用 resilience4j 作为我们整个框架实现重试机制的核心

image

再看下面一个场景:

微服务 A 通过同一个线程池调用微服务 B 的所有实例。如果有一个实例有问题,阻塞了请求,或者是响应非常慢。那么久而久之,这个线程池会被发送到这个异常实例的请求而占满,但是实际上微服务 B 是有正常工作的实例的。

为了防止这种情况,也为了限制调用每个微服务实例的并发(也就是限流),我们使用不同线程池调用不同的微服务的不同实例。这个也是通过 resilience4j 实现的。

image

如果一个实例在一段时间内压力过大导致请求慢,或者实例正在关闭,以及实例有问题导致请求响应大多是 500,那么即使我们有重试机制,如果很多请求都是按照请求到有问题的实例 -> 失败 -> 重试其他实例,这样效率也是很低的。这就需要使用断路器

在实际应用中我们发现,大部分异常情况下,是某个微服务的某些实例的某些接口有异常,而这些问题实例上的其他接口往往是可用的。所以我们的断路器不能直接将这个实例整个断路,更不能将整个微服务断路。所以,我们使用 resilience4j 实现的是微服务实例方法级别的断路器(即不同微服务,不同实例的不同方法是不同的断路器)。

image

本小节我们提出了一个简单的微服务架构,并仔细分析了其微服务实例的涉及的公共组件使用的库以及需要考虑的问题,并且针对微服务调用的核心 Http 客户端的重试机制,线程隔离机制和断路器机制需要考虑的问题以及如何设计做了较为详细的说明。接下来我们继续分析关于 Eureka 注册中心以及 API 网关设计需要考虑的机制。

上一篇 下一篇

猜你喜欢

热点阅读