Spring Cloud 升级之路 - 2020.0.x - 1. 背景知识、需求描述与公共依赖(上)

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: Spring Cloud 升级之路 - 2020.0.x - 1. 背景知识、需求描述与公共依赖(上)

1. 背景知识、需求描述与公共依赖


1.1. 背景知识 & 需求描述

Spring Cloud 官方文档说了,它是一个完整的微服务体系,用户可以通过使用 Spring Cloud 快速搭建一个自己的微服务系统。那么 Spring Cloud 究竟是如何使用的呢?他到底有哪些组件?

spring-cloud-commons组件里面,就有 Spring Cloud 默认提供的所有组件功能的抽象接口,有的还有默认实现。目前的 2020.0.x (按照之前的命名规则应该是 iiford),也就是spring-cloud-commons-3.0.x包括:

  • 服务发现DiscoveryClient,从注册中心发现微服务。
  • 服务注册ServiceRegistry,注册微服务到注册中心。
  • 负载均衡LoadBalancerClient,客户端调用负载均衡。其中,重试策略spring-cloud-commons-2.2.6加入了负载均衡的抽象中。
  • 断路器CircuitBreaker,负责什么情况下将服务断路并降级
  • 调用 http 客户端:内部 RPC 调用都是 http 调用

然后,一般一个完整的微服务系统还包括:

  1. 统一网关
  2. 配置中心
  3. 全链路监控与监控中心

在之前的系列中,我们将 Spring cloud 升级到了 Hoxton 版本,组件体系是:

  1. 注册中心:Eureka
  2. 客户端封装:OpenFeign
  3. 客户端负载均衡:Spring Cloud LoadBalancer
  4. 断路器与隔离: Resilience4J

并且实现了如下的功能:

注册中心相关

  1. 所有集群公用同一个公共 Eureka 集群
  2. 实现实例的快速上下线。

微服务实例相关

  1. 不同集群之间不互相调用,通过实例的metamap中的zone配置,来区分不同集群的实例。只有实例的metamap中的zone配置一样的实例才能互相调用。
  2. 微服务之间调用依然基于利用 open-feign 的方式,有重试,仅对GET请求并且状态码为4xx和5xx进行重试(对4xx重试是因为滚动升级的时候,老的实例没有新的 api,重试可以将请求发到新的实例上)
  3. 某个微服务调用其他的微服务 A 和微服务 B, 调用 A 和调用 B 的线程池不一样。并且调用不同实例的线程池也不一样。也就是实例级别的线程隔离
  4. 实现实例 + 方法级别的熔断,默认的实例级别的熔断太过于粗暴。实例上某些接口有问题,但不代表所有接口都有问题。
  5. 负载均衡的轮询算法,需要请求与请求之间隔离,不能共用同一个 position 导致某个请求失败之后的重试还是原来失败的实例。
  6. 对于 WebFlux 这种非 Servlet 的异步调用也实现相同的功能。


网关相关

  1. 通过metamap中的zone配置鉴别所处集群,仅把请求转发到相同集群的微服务实例
  2. 转发请求,有重试,仅对GET请求并且状态码为4xx和5xx进行重试
  3. 不同微服务的不同实例线程隔离
  4. 实现实例级别的熔断。
  5. 负载均衡的轮询算法,需要请求与请求之间隔离,不能共用同一个 position 导致某个请求失败之后的重试还是原来失败的实例
  6. 实现请求 body 修改(可能请求需要加解密,请求 body 需要打印日志,所以会涉及请求 body 的修改)

在后续的使用,开发,线上运行过程中,我们还遇到了一些问题:

  1. 业务在某些时刻,例如 6.30 购物狂欢,双 11 大促,双 12 剁手节,以及在法定假日的时候的快速增长,是很难预期的。虽然有根据实例 CPU 负载的扩容策略,但是这样也还是会有滞后性,还是会有流量猛增的时候导致核心业务(例如下单)有一段时间的不可用(可能5~30分钟)。主要原因是系统压力大之后导致很多请求排队,排队时间过长后等到处理这些请求时已经过了响应超时,导致本来可以正常处理的请求也没能处理。而且用户的行为就是,越是下不成单,越要刷新重试,这样进一步增加了系统压力,也就是雪崩。通过实例级别的线程隔离,我们限制了每个实例调用其他微服务的最大并发度,但是因为等待队列的存在还是具有排队。同时,在 API 网关由于没有做限流,由于 API 网关 Spring Cloud gateway 是异步响应式的,导致很多请求积压,进一步加剧了雪崩。所以这里,我们要考虑这些情况,重新设计线程隔离以及增加 API 网关限流。
  2. 微服务发现,未来为了兼容云原生应用,例如 K8s 的一些特性,最好服务发现是多个源
  3. 链路监控与指标监控是两套系统,使用麻烦,并且成本也偏高,是否可以优化成为一套。

接下来,我们要对现有依赖进行升级,并且对现有的功能进行一些拓展和延伸,形成一套完整的 Spring Cloud 微服务体系与监控体系。


1.2. 编写公共依赖


本次项目代码,请参考: https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford

这次我们抽象出更加具体的各种场景的依赖。一般的,我们的整个项目一般会包括:

  1. 公共工具包依赖:一般所有项目都会依赖一些第三方的工具库,例如 lombok, guava 这样的。对于这些依赖放入公共工具包依赖。
  2. 传统 servlet 同步微服务依赖:对于没有应用响应式编程而是用的传统 web servlet 模式的微服务的依赖管理。
  3. 响应式微服务依赖:对于基于 Project Reactor 响应式编程实现的微服务的依赖管理。响应式编程是一种大趋势,Spring 社区也在极力推广。可以从 Spring 的各个组件,尤其是 Spring Cloud 组件上可以看出来。spring-cloud-commons 更是对于微服务的每个组件抽象都提供了同步接口还有异步接口。我们的项目中也有一部分使用了响应式编程。

为何微服务要抽象分离出响应式的和传统 servlet 的呢

  1. 首先,Spring 官方其实还是很推崇响应式编程的,尤其是在 Hoxton 版本发布后, spring-cloud-commons 将所有公共接口都抽象了传统的同步版还有基于 Project Reactor 的异步版本。并且在实现上,默认的实现同步版的底层也是通过 Project Reactor 转化为同步实现的。可以看出,异步化已经是一种趋势。
  2. 但是, 异步化学习需要一定门槛,并且传统项目大多还是同步的,一些新组件或者微服务可以使用响应式实现。
  3. 响应式和同步式的依赖并不完全兼容,虽然同一个项目内同步异步共存,但是这种并不是官方推荐的做法(这种做法其实启动的 WebServer 还是 Servlet WebServer),并且 Spring Cloud gateway 这种实现的项目就完全不兼容,所以最好还是分离开来。
  4. 为什么响应式编程不普及主要因为数据库 IO,不是 NIO。不论是Java自带的Future框架,还是 Spring WebFlux,还是 Vert.x,他们都是一种非阻塞的基于Ractor模型的框架(后两个框架都是利用netty实现)。在阻塞编程模式里,任何一个请求,都需要一个线程去处理,如果io阻塞了,那么这个线程也会阻塞在那。但是在非阻塞编程里面,基于响应式的编程,线程不会被阻塞,还可以处理其他请求。举一个简单例子:假设只有一个线程池,请求来的时候,线程池处理,需要读取数据库 IO,这个 IO 是 NIO 非阻塞 IO,那么就将请求数据写入数据库连接,直接返回。之后数据库返回数据,这个链接的 Selector 会有 Read 事件准备就绪,这时候,再通过这个线程池去读取数据处理(相当于回调),这时候用的线程和之前不一定是同一个线程。这样的话,线程就不用等待数据库返回,而是直接处理其他请求。这样情况下,即使某个业务 SQL 的执行时间长,也不会影响其他业务的执行。但是,这一切的基础,是 IO 必须是非阻塞 IO,也就是 NIO(或者 AIO)。官方JDBC没有 NIO,只有 BIO 实现(因为官方是 Oracle 提供维护,但是 Oracle 认为下面会提到的 Project Loom 是可以解决同步风格代码硬件效率低下的问题的,所以一直不出)。这样无法让线程将请求写入链接之后直接返回,必须等待响应。但是也就解决方案,就是通过其他线程池,专门处理数据库请求并等待返回进行回调,也就是业务线程池 A 将数据库 BIO 请求交给线程池B处理,读取完数据之后,再交给 A 执行剩下的业务逻辑。这样A也不用阻塞,可以处理其他请求。但是,这样还是有因为某个业务 SQL 的执行时间长,导致B所有线程被阻塞住队列也满了从而A的请求也被阻塞的情况,这是不完美的实现。真正完美的,需要 JDBC 实现 NIO。
  5. Java 响应式编程的未来会怎样是否会有另一种解决办法?我个人觉得,如果有兴趣可以研究下响应式编程 WebFlux,但是不必强求一定要使用响应式编程。虽然异步化编程是大趋势,响应式编程越来越被推崇,但是 Java 也有另外的办法解决同步式编码带来的性能瓶颈,也就是 Project LoomProject Loom 可以让你继续使用同步风格写代码,在底层用的其实是非阻塞轻量级虚拟线程,网络 IO 是不会造成系统线程阻塞的,但是目前 sychronized 以及本地文件 IO 还是会造成阻塞。不过,主要问题是解决了的。所以,本系列还是会以同步风格代码和 API 为主。

1.2.1. 公共 parent

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.github.hashjang</groupId>
    <artifactId>spring-cloud-iiford</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.version>1.0-SNAPSHOT</project.version>
    </properties>
    <dependencies>
        <!--junit单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <!--spring-boot单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--mockito扩展,主要是需要mock final类-->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>3.6.28</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2020.0.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <!--最好用JDK 12版本及以上编译,11.0.7对于spring-cloud-gateway有时候编译会有bug-->
                    <!--虽然官网说已解决,但是11.0.7还是偶尔会出现-->
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
1月前
|
消息中间件 监控 Java
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
38 6
|
1月前
|
Java 关系型数据库 MySQL
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
62 5
|
1月前
|
缓存 监控 Java
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
44 5
|
6月前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
660 15
|
5月前
|
API 开发者 Java
API 版本控制不再难!Spring 框架带你玩转多样化的版本管理策略,轻松应对升级挑战!
【8月更文挑战第31天】在开发RESTful服务时,为解决向后兼容性问题,常需进行API版本控制。本文以Spring框架为例,探讨四种版本控制策略:URL版本控制、请求头版本控制、查询参数版本控制及媒体类型版本控制,并提供示例代码。此外,还介绍了通过自定义注解与过滤器实现更灵活的版本控制方案,帮助开发者根据项目需求选择最适合的方法,确保API演化的管理和客户端使用的稳定与兼容。
228 0
|
6月前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
139 3
|
4月前
|
SpringCloudAlibaba API 开发者
新版-SpringCloud+SpringCloud Alibaba
新版-SpringCloud+SpringCloud Alibaba
|
16天前
|
SpringCloudAlibaba 负载均衡 Dubbo
【SpringCloud Alibaba系列】Dubbo高级特性篇
本章我们介绍Dubbo的常用高级特性,包括序列化、地址缓存、超时与重试机制、多版本、负载均衡。集群容错、服务降级等。
【SpringCloud Alibaba系列】Dubbo高级特性篇
|
16天前
|
存储 SpringCloudAlibaba Java
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论。
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
|
16天前
|
SpringCloudAlibaba JavaScript Dubbo
【SpringCloud Alibaba系列】Dubbo dubbo-admin安装教程篇
本文介绍了 Dubbo-Admin 的安装和使用步骤。Dubbo-Admin 是一个前后端分离的项目,前端基于 Vue,后端基于 Spring Boot。安装前需确保开发环境(Windows 10)已安装 JDK、Maven 和 Node.js,并在 Linux CentOS 7 上部署 Zookeeper 作为注册中心。
【SpringCloud Alibaba系列】Dubbo dubbo-admin安装教程篇