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 调用
然后,一般一个完整的微服务系统还包括:
- 统一网关
- 配置中心
- 全链路监控与监控中心
在之前的系列中,我们将 Spring cloud 升级到了 Hoxton 版本,组件体系是:
- 注册中心:Eureka
- 客户端封装:OpenFeign
- 客户端负载均衡:Spring Cloud LoadBalancer
- 断路器与隔离: Resilience4J
并且实现了如下的功能:
注册中心相关:
- 所有集群公用同一个公共 Eureka 集群。
- 实现实例的快速上下线。
微服务实例相关:
- 不同集群之间不互相调用,通过实例的
metamap
中的zone
配置,来区分不同集群的实例。只有实例的metamap
中的zone
配置一样的实例才能互相调用。 - 微服务之间调用依然基于利用 open-feign 的方式,有重试,仅对GET请求并且状态码为4xx和5xx进行重试(对4xx重试是因为滚动升级的时候,老的实例没有新的 api,重试可以将请求发到新的实例上)
- 某个微服务调用其他的微服务 A 和微服务 B, 调用 A 和调用 B 的线程池不一样。并且调用不同实例的线程池也不一样。也就是实例级别的线程隔离
- 实现实例 + 方法级别的熔断,默认的实例级别的熔断太过于粗暴。实例上某些接口有问题,但不代表所有接口都有问题。
- 负载均衡的轮询算法,需要请求与请求之间隔离,不能共用同一个 position 导致某个请求失败之后的重试还是原来失败的实例。
- 对于 WebFlux 这种非 Servlet 的异步调用也实现相同的功能。
网关相关:
- 通过
metamap
中的zone
配置鉴别所处集群,仅把请求转发到相同集群的微服务实例 - 转发请求,有重试,仅对GET请求并且状态码为4xx和5xx进行重试
- 不同微服务的不同实例线程隔离
- 实现实例级别的熔断。
- 负载均衡的轮询算法,需要请求与请求之间隔离,不能共用同一个 position 导致某个请求失败之后的重试还是原来失败的实例
- 实现请求 body 修改(可能请求需要加解密,请求 body 需要打印日志,所以会涉及请求 body 的修改)
在后续的使用,开发,线上运行过程中,我们还遇到了一些问题:
- 业务在某些时刻,例如 6.30 购物狂欢,双 11 大促,双 12 剁手节,以及在法定假日的时候的快速增长,是很难预期的。虽然有根据实例 CPU 负载的扩容策略,但是这样也还是会有滞后性,还是会有流量猛增的时候导致核心业务(例如下单)有一段时间的不可用(可能5~30分钟)。主要原因是系统压力大之后导致很多请求排队,排队时间过长后等到处理这些请求时已经过了响应超时,导致本来可以正常处理的请求也没能处理。而且用户的行为就是,越是下不成单,越要刷新重试,这样进一步增加了系统压力,也就是雪崩。通过实例级别的线程隔离,我们限制了每个实例调用其他微服务的最大并发度,但是因为等待队列的存在还是具有排队。同时,在 API 网关由于没有做限流,由于 API 网关 Spring Cloud gateway 是异步响应式的,导致很多请求积压,进一步加剧了雪崩。所以这里,我们要考虑这些情况,重新设计线程隔离以及增加 API 网关限流。
- 微服务发现,未来为了兼容云原生应用,例如 K8s 的一些特性,最好服务发现是多个源
- 链路监控与指标监控是两套系统,使用麻烦,并且成本也偏高,是否可以优化成为一套。
接下来,我们要对现有依赖进行升级,并且对现有的功能进行一些拓展和延伸,形成一套完整的 Spring Cloud 微服务体系与监控体系。
1.2. 编写公共依赖
本次项目代码,请参考:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford
这次我们抽象出更加具体的各种场景的依赖。一般的,我们的整个项目一般会包括:
- 公共工具包依赖:一般所有项目都会依赖一些第三方的工具库,例如 lombok, guava 这样的。对于这些依赖放入公共工具包依赖。
- 传统 servlet 同步微服务依赖:对于没有应用响应式编程而是用的传统 web servlet 模式的微服务的依赖管理。
- 响应式微服务依赖:对于基于 Project Reactor 响应式编程实现的微服务的依赖管理。响应式编程是一种大趋势,Spring 社区也在极力推广。可以从 Spring 的各个组件,尤其是 Spring Cloud 组件上可以看出来。spring-cloud-commons 更是对于微服务的每个组件抽象都提供了同步接口还有异步接口。我们的项目中也有一部分使用了响应式编程。
为何微服务要抽象分离出响应式的和传统 servlet 的呢?
- 首先,Spring 官方其实还是很推崇响应式编程的,尤其是在 Hoxton 版本发布后, spring-cloud-commons 将所有公共接口都抽象了传统的同步版还有基于 Project Reactor 的异步版本。并且在实现上,默认的实现同步版的底层也是通过 Project Reactor 转化为同步实现的。可以看出,异步化已经是一种趋势。
- 但是, 异步化学习需要一定门槛,并且传统项目大多还是同步的,一些新组件或者微服务可以使用响应式实现。
- 响应式和同步式的依赖并不完全兼容,虽然同一个项目内同步异步共存,但是这种并不是官方推荐的做法(这种做法其实启动的 WebServer 还是 Servlet WebServer),并且 Spring Cloud gateway 这种实现的项目就完全不兼容,所以最好还是分离开来。
- 为什么响应式编程不普及?主要因为数据库 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。
- Java 响应式编程的未来会怎样?是否会有另一种解决办法?我个人觉得,如果有兴趣可以研究下响应式编程 WebFlux,但是不必强求一定要使用响应式编程。虽然异步化编程是大趋势,响应式编程越来越被推崇,但是 Java 也有另外的办法解决同步式编码带来的性能瓶颈,也就是 Project Loom。Project 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>
1.2.2. 公共基础依赖包
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>spring-cloud-iiford</artifactId><groupId>com.github.hashjang</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>spring-cloud-iiford-common</artifactId><properties><guava.version>30.1.1-jre</guava.version><fastjson.version>1.2.75</fastjson.version><disruptor.version>3.4.2</disruptor.version><jaxb.version>2.3.1</jaxb.version><activation.version>1.1.1</activation.version></properties><dependencies><!--内部缓存框架统一采用caffeine--><!--这样Spring cloud loadbalancer用的本地实例缓存也是基于Caffeine--><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId></dependency><!-- guava 工具包 --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>${guava.version}</version></dependency><!--内部序列化统一采用fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><!--日志需要用log4j2--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><!--lombok简化代码--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--log4j2异步日志需要的依赖,所有项目都必须用log4j2和异步日志配置--><dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>${disruptor.version}</version></dependency><!--JDK 9之后的模块化特性导致javax.xml不自动加载,所以需要如下模块--><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>${jaxb.version}</version></dependency><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>${jaxb.version}</version></dependency><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>${jaxb.version}</version></dependency><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-xjc</artifactId><version>${jaxb.version}</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>${activation.version}</version></dependency></dependencies>
</project>
1. 缓存框架 caffeine
很高效的本地缓存框架,接口设计与 Guava-Cache 完全一致,可以很容易地升级。性能上,caffeine 源码里面就有和 Guava-Cache, ConcurrentHashMap,ElasticSearchMap,Collision 和 Ehcache 等等实现的对比测试,并且测试给予了 yahoo 测试库,模拟了近似于真实用户场景,并且,caffeine 参考了很多论文实现不同场景适用的缓存,例如:
- Adaptive Replacement Cache:http://www.cs.cmu.edu/~15-440/READINGS/megiddo-computer2004.pdf
2.Quadruply-segmented LRU:http://www.cs.cornell.edu/~qhuang/papers/sosp_fbanalysis.pdf - 2 Queue:http://www.tedunangst.com/flak/post/2Q-buffer-cache-algorithm
- Segmented LRU:http://www.is.kyusan-u.ac.jp/~chengk/pub/papers/compsac00_A07-07.pdf
- Filtering-based Buffer Cache:http://storageconference.us/2017/Papers/FilteringBasedBufferCacheAlgorithm.pdf
所以,我们选择 caffeine 作为我们的本地缓存框架
参考:https://github.com/ben-manes/caffeine
2. guava
guava 是 google 的 Java 库,虽然本地缓存我们不使用 guava,但是 guava 还有很多其他的元素我们经常用到。
参考:https://guava.dev/releases/snapshot-jre/api/docs/
3. 内部序列化从 fastjson 改为 jackson
json 库一般都需要预热一下,后面会提到怎么做。
我们项目中有一些内部序列化是 fastjson 序列化,但是看 fastjson 已经很久没有更新,有很多 issue 了,为了避免以后出现问题(或者漏洞,或者性能问题)增加线上可能的问题点,我们这一版本做了兼容。在下一版本会把 fastjson 去掉。后面会详细说明如何去做。
4. 日志采用 log4j2
主要是看中其异步日志的特性,让打印大量业务日志不成为性能瓶颈。但是,还是不建议在线上环境输出代码行等位置信息,具体原因以及解决办法后面会提到。由于 log4j2 异步日志特性依赖 disruptor,还需要加入 disruptor 的依赖。
参考:
- https://logging.apache.org/log4j/2.x/
- https://lmax-exchange.github.io/disruptor/
5. 兼容 JDK 9+ 需要添加的一些依赖
JDK 9之后的模块化特性导致 javax.xml 不自动加载,而项目中的很多依赖都需要这个模块,所以手动添加了这些依赖。
1.2.3. Servlet 微服务公共依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>spring-cloud-iiford</artifactId><groupId>com.github.hashjang</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>spring-cloud-iiford-service-common</artifactId><dependencies><dependency><groupId>com.github.hashjang</groupId><artifactId>spring-cloud-iiford-common</artifactId><version>${project.version}</version></dependency><!--注册到eureka--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!--不用Ribbon,用Spring Cloud LoadBalancer--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-loadbalancer</artifactId></dependency><!--微服务间调用主要靠 openfeign 封装 API--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--resilience4j 作为重试,断路,限并发,限流的组件基础--><dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-cloud2</artifactId></dependency><!-- https://mvnrepository.com/artifact/io.github.resilience4j/resilience4j-feign --><dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-feign</artifactId></dependency><!--actuator接口--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--调用路径记录--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId></dependency><!--暴露actuator相关端口--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--暴露http接口, servlet框架采用nio的undertow,注意直接内存使用,减少GC--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency></dependencies>
</project>
这里面相关的依赖,我们后面会用到。
1.2.4. Webflux 微服务相关依赖
对于 Webflux 响应式风格的微服务,其实就是将 spring-boot-starter-web
替换成 spring-boot-starter-webflux
即可
参考:pom.xml
Spring Cloud 升级之路 - 2020.0.x - 1. 背景知识、需求描述与公共依赖相关推荐
- Spring Cloud升级之路 - Hoxton - 8. 修改实例级别的熔断为实例+方法级别
实例级别的熔断带来的困扰 如之前系列(Spring Cloud升级之路 - Hoxton - 4. 使用Resilience4j实现实例级别的隔离与熔断)所述,我们实现了实例级别的熔断.但是在生产中发 ...
- SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...
- 2021-08-05SpringCloud升级之路2020.0.x版-5.所有项目的parent与spring-framework-common说明
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 源代码文件:htt ...
- Spring Cloud升级之路 - Hoxton - 10. 网关重试带Body的请求Body丢失的问题
带 Body 的重试 Body 丢失 之前我们的配置里面,只对 Get 请求针对 500 响应码重试,但是针对像 Post 这样的请求,只对那种根本还没到发送 Body 的阶段的异常(例如连接异常)这 ...
- SpringCloud升级之路2020.0.x版-26.OpenFeign的组件
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 首先,我们给出官方文档中的组件结构图: [外链图片转存失败,源站可能有防盗链机制,建议将图 ...
- SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(3)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们继续分析上一节提到的 WebHandler.加入 Spring Cloud Sleut ...
- SpringCloud升级之路2020.0.x版-13.UnderTow 核心配置
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford Undertow ...
- SpringCloud升级之路2020.0.x版-12.UnderTow 简介与内部原理
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 在我们的项目中,我 ...
- Spring Cloud面试题(2020最新版)
转载自 Spring Cloud面试题(2020最新版) 为什么需要学习Spring Cloud 不论是商业应用还是用户应用,在业务初期都很简单,我们通常会把它实现为单体结构的应用.但是,随着业务逐 ...
最新文章
- 基于caffe的度量学习实现(Siamese network Triplet network)
- 一步步带你详解JVM性能调优
- pythreejs is needed for plotting with pythreejs backend
- optee系统服务/service的实现方式
- 对口计算机选什么专业,想从事人工智能行业,考研选什么专业对口?
- osg 倾斜数据纹理_GIS数据、服务、数据库
- Oracle全库导入表报错 ORA-39126
- python虚拟环境 windows环境搭建_windows下安装Python虚拟环境virtualenvwrapper-win
- 使用rvm来管理ruby版本
- 哈工大学习笔记 | 图文并茂详解隐马尔可夫模型
- 鬼谷八荒steam修改器|鬼谷八荒多功能修改器
- 《photoshopCS4中文版完全自学手册》视频教程
- 视觉目标跟踪大牛主页
- Webots中创建舵轮模型
- 什么索引?索引的作用是什么?索引运用实例
- 网口扫盲三:以太网芯片MAC和PHY的关系
- 关于服务器磁盘配额超出的问题解决(报错源码是Disk quota exceeded)
- GitHub设置代理 提升速度
- 微信小程序之文本换行居中
- 最大规模开源说话人识别语料集——VoxCeleb
热门文章
- lnln(10 3.5 2)的c语言,ln10(ln对数表)
- Android 自定义相机 身份证拍照 自定义身份证相机
- Matlab中一球反弹的高度,matlab数学建模2乒乓球的弹跳和罗基斯帝模型.doc
- 大学生信息安全(学习笔记一)
- linux中户主目录,如何在Ubuntu 18.04 Bionic Beaver Linux上配置Samba服务器共享
- 【为什么换了固态硬盘电脑会快?详解硬盘与内存的关系】
- 个人博客标签和文章的表结构设计
- c语言luhn算法生成校验位,银行卡号码校验算法(Luhn算法,又叫模10算法)
- 专利申请的流程与时间
- Unity 设置代理 下载资源