Spring Cloud Alibaba 架构

毫无疑问,Spring Cloud 是目前微服务架构领域的翘楚,实际上,Spring Cloud 是一个全家桶式的技术栈,包含了很多组件。本文先从其最核心的几个组件入手,来剖析一下其底层的工作原理,也就是 Nacos、LoadBalancer、Feign、Sentinel、GateWay 这几个组件

业务场景介绍

假设需求为开发一个电商网站,要实现支付订单的功能,流程如下

  • 创建一个订单之后,如果用户立刻支付了这个订单,我们需要将订单状态更新为"已支付"

  • 扣减相应的商品库存

  • 通知仓储中心,进行发货

  • 给用户的这次购物增加相应的积分

针对上述流程,我们需要有订单服务,库存服务,仓储服务,积分服务,整个流程的大体思路如下:

  1. 用户针对一个订单完成支付之后,查看订单服务,更新订单状态

  2. 订单服务调用库存服务,完成相应功能

  3. 订单服务调用仓储服务,完成相应功能

  4. 订单服务调用积分服务,完成相应功能

服务注册与服务发现–Nacos

订单服务想要调用库存服务、仓储服务或积分服务,需要硬编码其他服务的地址,在服务地址变更时不能很好的响应变更,针对这个问题,可以采用 SpringCloud 架构内的服务发现与注册中心来动态维护服务列表与负载均衡

Nacos 是 Spring Cloud Alibaba 提供的微服务注册中心,专门负责服务的注册与发现

Nacos 基本架构图如下

Nacos架构图

如上图所示,库存服务、仓储服务、积分服务中都有一个 Nacos Client 组件,这个组件专门负责注册服务,而 Nacos Server 是一个注册中心,内部维护了一张注册表,保存了各服务所在的机器和端口号

Nacos Client 组件负责向 Nacos Server 轮询服务相关信息,把这些相关信息从 Nacos Server 的注册表中拉取至本地缓存,同时也承担了发送心跳包,报告服务健康状态等功能

主要组件的作用如下

  • Nacos Client:负责将服务信息注册到 Nacos Server 中,同时维护微服务的可用性

  • Nacos Server:注册中心,维护注册表,表中保存了各个服务所在的机器和端口号

若订单服务想要调用库存服务,可以对服务内的 Nacos Client 发起查询,Nacos Client 会从本地的缓存内查询对应的地址与端口号,通过 Fegin 等远程调用 API 向对应的地址与端口发起请求

声明式 HTTP 客户端–Feign

在配置完毕 Nacos 的服务发现与服务注册之后,又出现了新的问题,若微服务需要调用其他模块的服务时,虽然通过 Nacos 可以获取其他服务的地址与端口号,但是仍需手动建立网络连接,构造复杂的请求并发送,还需要处理返回的请求等几步冗余操作

针对这个问题,Feign 为我们提供了优雅的解决方案,Feign 使用了动态代理来自动构建请求和解析响应,主要流程如下

  • 对定义了@FeignClient注解的接口创建一个动态代理

  • 若调用该接口,本质就是会调用 Feign 创建的动态代理

  • Feign 的动态代理会根据你在接口上的@RequestMapping等注解来动态构造请求服务的地址

  • 针对这个地址发起请求并解析响应

Feign架构图

负载均衡–Spring Cloud Loadbalancer

在较旧的版本中 Spring Cloud Alibaba 使用 Robbin 作为负载均衡实现

一般使用 Feign 不仅是需要指定服务地址,Feign 的使用一般与负载均衡场景相结合使用,若库存服务存在 5 个可用实例

  • 192.168.31.169:9000

  • 192.168.31.170:9000

  • 192.168.31.171:9000

  • 192.168.31.172:9000

  • 192.168.31.173:9000

Spring Cloud Loadbalancer 可以为当前的服务提供负载均衡特性,在每次请求时选择一台机器,均匀的把请求分发到各个机器上

Loadbalancer 是和 Feign 以及 Nacos 紧密协作完成工作的

  1. 首先 Loadbalancer 会从 Nacos Client 里获取到对应的服务注册表

  2. Loadbalancer 就可以使用默认的负载均衡算法,从中选择一台机器

  3. Feign 针对这台机器,构造并发起请求

上述整个过程的流程图如下,在 Feign 的流程图基础上增加了 Loadbalancer 与 Nacos 图例

负载均衡

服务容错–Sentinel

服务雪崩

在微服务架构里,一个系统会有很多的服务,以本文的业务场景为例,订单服务在一个业务流程里需要调用三个服务

现在假设订单服务的线程池容量为 100,有 100 个线程可以处理请求,但积分服务由于某些原因无可用实例,这样每次订单服务调用积分服务的时候,都会抛出超时异常,这样的异常一层层向上扩大,最终导致整个订单系统瘫痪,无可用线程用于处理订单请求,此即微服务中的服务雪崩现象

服务雪崩效应是一种因"服务提供者的不可用"(原因)导致"服务调用者不可用"(结果),并将不可用逐渐放大的现象

服务雪崩的过程可以分为三个阶段

  • 服务提供者不可用

  • 重试加大请求流量

  • 服务调用者不可用

服务雪崩

如上图,多个微服务互相调用,若没有任何保护的话,某一个服务发生错误,就会引起连锁反应,最终导致整个系统宕机

但是对于微服务来说,各个模块之间的合理划分能将耦合度降低,换句话说就是没有很强的依赖性,按照该例子的使用场景,就算积分服务宕机,订单服务也可以正常运行

结合业务来看:主要的处理流程为:支付订单,扣减库存,仓储发货与增加积分,每个流程处理都相对独立,同样的情况如果仓储服务宕机,之前的处理流程同样可以正常运行

Sentinel 可以在当调用链路中某个资源出现不稳定时对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生服务雪崩

例如当前微服务表现为 timeout,异常比例升高等

服务降级

服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些业务场景出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性

需要考虑的问题:

  • 区分哪些服务为核心服务

  • 降级策略以及处理方式,一般指如何给用户友好的提示或者操作

  • 自动降级还是手动降级

服务熔断

服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路

Sentinel 对熔断降级问题采取了两种手段

通过并发线程数进行限制

和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响,类似信号量隔离

Hystrix 是隔离、熔断以及降级的一个框架,采用了资源池隔离的方法实现服务熔断,每个线程池里的线程就仅仅用于请求那个服务

这样不但没有线程切换的损耗,也不需要预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求

通过响应时间对资源进行降级

除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复

Sentinel 熔断与降级的原理示意

服务熔断

Sentinel 与基于线程池隔离的限流模式的区别

Sentinel 基于并发线程数的流量控制, 能以资源为维度, 为不同的资源, 配置不用的线程数, 控制访问每个资源的线程数,

有点类似于信号量方式, 信号量的资源数即为并发线程数

采用基于线程数的限流模式后,我们不需要再显式地去进行线程池隔离,Sentinel 会控制访问该资源的线程数,超出的请求直接拒绝,直到堆积的线程处理完成。相当于是以资源为维度, 隔离出了每一种资源对应的不同线程数

例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽

为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)

这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离

API 网关–GateWay

微服务网关是整个微服务 API 请求的入口,可以实现过滤 Api 接口

Zuul 网关属于 netfix 公司开源的产品,属于第一代微服务网关

Gateway 属于 SpringCloud 自研发的网关框架,属于第二代微服务网关

相比来说 SpringCloudGateway 性能比 Zuul 性能要好

注意:Zuul 网关底层基于 Servlet 实现的,阻塞式的 Api,不支持长连接。
SpringCloudGateway 基于 Spring5 构建,能够实现响应式非阻塞式的 Api,支持长连接,能够更好的整合 Spring 体系的产品,依赖 SpringBoot-WebFux

作用:可以实现用户的验证登录、解决跨域、日志拦截、权限控制、限流、熔断、负载均衡、黑名单与白名单机制等。

微服务中的架构模式采用前后端分离,前端调用接口地址都能够被抓包分析到

过滤器与网关的区别:

过滤器适合于单个服务实现过滤请求

网关拦截整个的微服务实现过滤请求,能够解决整个微服务中冗余代码

过滤器是局部拦截,网关实现全局拦截

总结

最后再来总结一下,上述几个 Spring Cloud 核心组件,在微服务架构中,分别扮演的角色:

  • Nacos:各个服务启动时,Nacos Client 都会将服务注册到 Nacos Server,且 Nacos Client 从 Nacos Server 拉取注册表

  • Spring Cloud Loadbalancer:服务间发起请求时,基于 Loadbalancer 算法实现负载均衡

  • Feign:基于 Feign 的动态代理机制,拼接请求 URL 地址,发起请求

  • Sentinel:实现了不同服务调用的隔离,保证了微服务的可用性以及实现流量控制

  • Spring Cloud GateWay:统一从网关进入,由网关转发请求给对应的服务

以上就是我们通过一个电商业务场景,阐述了 Spring Cloud 微服务架构几个核心组件的底层原理

SpringCloud整体架构图