微服务
微服务最初是由 Martin Fowler 于 2014 年发表的论文 《MicroServices》 中提出的名词。- 所谓 服务,指的是项目中的功能模块,它可以帮助用户解决某一个或一组问题,在开发过程中表现为 IDE(集成开发环境,例如 Eclipse 或 IntelliJ IDEA)中的一个工程或 Moudle。
- 微小 强调的是单个服务的服务体积小,复杂度低。一个微服务通常只提供单个业务功能的服务,即一个微服务只专注于做好一件事,因此微服务通常代码较少,体积较小,复杂度也较低。
微服务架构
微服务架构是一种系统架构的设计风格。与传统的单体式架构(ALL IN ONE)不同,微服务架构提倡将一个单一的应用程序拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间使用轻量级通信机制(通常是 HTTP RESTFUL API)进行通讯。通常情况下,这些小型服务都是围绕着某个特定的业务进行构建的,每一个服务只专注于完成一项任务并把它做好 ,即“专业的人做专业的事”。
每个服务都能够独立地部署到各种环境中,例如开发环境、测试环境和生产环境等,每个服务都能独立启动或销毁而不会对其他服务造成影响。
这些服务之间的交互是使用标准的通讯技术进行的,因此不同的服务可以使用不同数据存储技术,甚至使用不同的编程语言。在当今的软件开发领域中,主要有两种系统架构风格,那就是新兴的 微服务架构** 和传统的 单体架构**。
单体架构是微服务架构出现之前业界最经典的软件架构类型,许多早期的项目采用的也都是单体架构。单体架构将应用程序中所有业务逻辑都编写在同一个工程中,最终经过编译、打包,部署在一台服务器上运行。
在项目的初期,单体架构无论是在开发速度还是运维难度上都具有明显的优势。但随着业务复杂度的不断提高,单体架构的许多弊端也逐渐凸显出来,主要体现在以下 3 个方面:
- 随着业务复杂度的提高,单体应用(采用单体架构的应用程序)的代码量也越来越大,导致代码的可读性、可维护性以及扩展性下降。
- 随着用户越来越多,程序所承受的并发越来越高,而单体应用处理高并发的能力有限。
- 单体应用将所有的业务都集中在同一个工程中,修改或增加业务都可能会对其他业务造成一定的影响,导致测试难度增加。
微服务的特点
优点:
- 服务按照业务来划分,每个服务通常只专注于某一个特定的业务、所需代码量小,复杂度低、易于维护。
- 每个微服都可以独立开发、部署和运行,且代码量较少,因此启动和运行速度较快。
- 采用单体架构的应用程序只要有任何修改,就需要重新部署整个应用才能生效,而微服务则完美地解决了这一问题。在微服架构中,某个微服务修改后,只需要重新部署这个服务即可,而不需要重新部署整个应用程序。
- 在微服务架构中,开发人员可以结合项目业务及团队的特点,合理地选择语言和工具进行开发和部署,不同的微服务可以使用不同的语言和工具。
- 微服务具备良好的可扩展性。随着业务的不断增加,微服务的体积和代码量都会急剧膨胀,此时我们可以根据业务将微服务再次进行拆分;除此之外,当用户量和并发量的增加时,我们还可以将微服务集群化部署,从而增加系统的负载能力。
- 微服务能够与容器(Docker)配合使用,实现快速迭代、快速构建、快速部署。
- 微服务具有良好的故障隔离能力,当应用程序中的某个微服发生故障时,该故障会被隔离在当前服务中,而不会波及到其他微服务造成整个系统的瘫痪。
- 微服务系统具有链路追踪的能力。
缺点:
- 分布式部署,调用的复杂性高:单体应用的时候,所有模块之前的调用都是在本地进行的,在微服务中,每个模块都是独立部署的,通过 HTTP 来进行通信,这当中会产生很多问题,比如网络问题、容错问题、调用关系等。
- 独立的数据库,分布式事务的挑战:每个微服务都有自己的数据库,这就是所谓的去中心化的数据管理。这种模式的优点在于不同的服务,可以选择适合自身业务的数据,比如订单服务可以用 MySQL、评论服务可以用 MongoDB、商品搜索服务可以用 Elasticsearch。
- 测试的难度提升:服务和服务之间通过接口来交互,当接口有改变的时候,对所有的调用方都是有影响的,这时自动化测试就显得非常重要了,如果要靠人工一个个接口去测试,那工作量就太大了。这里要强调一点,就是 API 文档的管理尤为重要。
- 运维难度的提升:在采用传统的单体应用时,我们可能只需要关注一个 Tomcat 的集群、一个 MySQL 的集群就可以了,但这在微服务架构下是行不通的。当业务增加时,服务也将越来越多,服务的部署、监控将变得非常复杂,这个时候对于运维的要求就高了。
SpringCloud
Spring Cloud 是分布式微服务架构的一站式解决方案,它并不是某一门技术,它是多种微服务架构落地技术的集合体,俗称微服务全家桶。它将市面上成熟的、经过验证的微服务框架整合起来,并通过 Spring Boot 的思想进行再封装,屏蔽调其中复杂的配置和实现原理,最终为开发人员提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Spring Cloud 并不是一个拿来即可用的框架,它是一种微服务规范,有以下 2 代实现:- 第一代实现:Spring Cloud Netflix
- 第二代实现:Spring Cloud Alibaba
Spring Cloud 组件 | 描述 |
---|---|
Spring Cloud Netflix Eureka | Spring Cloud Netflix 中的服务治理组件,包含服务注册中心、服务注册与发现机制的实现。 |
Spring Cloud Netflix Ribbon | Spring Cloud Netflix 中的服务调用和客户端负载均衡组件。 |
Spring Cloud Netflix Hystrix | 人称“豪猪哥”,Spring Cloud Netflix 的容错管理组件,为服务中出现的延迟和故障提供强大的容错能力。 |
Spring Cloud Netflix Feign | 基于 Ribbon 和 Hystrix 的声明式服务调用组件。 |
Spring Cloud Netflix Zuul | Spring Cloud Netflix 中的网关组件,提供了智能路由、访问过滤等功能。 |
Spring Cloud Gateway | 一个基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关框架,它使用 Filter 链的方式提供了网关的基本功能,例如安全、监控/指标和限流等。 |
Spring Cloud Config | Spring Cloud 的配置管理工具,支持使用 Git 存储配置内容,实现应用配置的外部化存储,并支持在客户端对配置进行刷新、加密、解密等操作。 |
Spring Cloud Bus | Spring Cloud 的事件和消息总线,主要用于在集群中传播事件或状态变化,以触发后续的处理,例如动态刷新配置。 |
Spring Cloud Stream | Spring Cloud 的消息中间件组件,它集成了 Apache Kafka 和 RabbitMQ 等消息中间件,并通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件之间的隔离。通过向应用程序暴露统一的 Channel 通道,使得应用程序不需要再考虑各种不同的消息中间件实现,就能轻松地发送和接收消息。 |
Spring Cloud Sleuth | Spring Cloud 分布式链路跟踪组件,能够完美的整合 Twitter 的 Zipkin。 |
SpringCloud命名
老版本命名
为了避免 Spring Cloud 的版本号与其子项目的版本号混淆,Spring Cloud 没有采用常见的数字版本号,而是通过以下方式定义Spring Cloud 版本信息说明如下:
{version.name} .{version.number}
- version.name:版本名,采用英国伦敦地铁站的站名来命名,并按照字母表的顺序(即从 A 到 Z)来对应 Spring Cloud 的版本发布顺序,例如第一个版本为 Angel,第二个版本为 Brixton(英国地名),然后依次是
<font style="color:rgb(68, 68, 68);">Camden、Dalston、Edgware、Finchley、Greenwich、Hoxton</font>
等。 - version.number:版本号,每一个版本的 Spring Cloud 在更新内容积累到一定的量级或有重大 BUG 修复时,就会发布一个
<font style="color:rgb(68, 68, 68);">service releases</font>
版本,简称<font style="color:rgb(68, 68, 68);">SRX</font>
版本,其中<font style="color:rgb(68, 68, 68);">X</font>
为一个递增的数字,例如 Hoxton.SR8 就表示 Hoxton 的第 8 个 Release 版本。
新版本命名
在2020年4月17日官方就发布了 <font style="color:rgb(0, 0, 0);">Spring Cloud 2020.0.0-M1</font>
,可能是考虑到Spring Cloud发展太迅速地铁站名不够用😂,另外用日期命名也可读性更高。
从发布信息可以看出,从 Spring Cloud 2020.0.0-M1 开始,Spring Cloud将不再使用英国伦敦地铁站的命名方式,而使用了全新的 “日历化”(Calendar Versioning或简称CalVer) 版本命名方式。
Spring Cloud版本号将使用了<font style="color:black;">YYYY.MINOR.MICRO</font>
的命名规则,其中:
- YYYY:代表4 位年份
- MINOR:表示一个每年以 0 开始递增的数字
- MICRO:表示版本号的后缀,
<font style="color:rgb(68, 68, 68);">.0</font>
类似于<font style="color:rgb(68, 68, 68);">.RELEASE</font>
一样,<font style="color:rgb(68, 68, 68);">.2</font>
类似于<font style="color:rgb(68, 68, 68);">.SR2</font>
- 预发布版本的后缀分隔符也由
<font style="color:rgb(68, 68, 68);">.</font>
变为<font style="color:rgb(68, 68, 68);">-</font>
,如:<font style="color:rgb(68, 68, 68);">2020.0.0-M1</font>
和<font style="color:rgb(68, 68, 68);">2020.0.0-RC2</font>
命名所示
版本选择
用 Spring Boot + Spring Cloud 进行微服务开发时,我们需要根据项目中 Spring Boot 的版本来决定 Spring Cloud 版本,否则会出现许多意想不到的错误。 Spring Boot 与 Spring Cloud 的版本对应关系如下表(参考自 Spring Cloud 官网)<font style="color:rgb(51, 51, 51);">Cloud </font> Version |
<font style="color:rgb(51, 51, 51);">Boot</font> Version |
---|---|
2021.0.x aka Jubilee | 2.6.x, 2.7.x (Starting with 2021.0.3) |
2020.0.x aka Ilford | 2.4.x, 2.5.x (Starting with 2020.0.3) |
Hoxton | 2.2.x, 2.3.x (Starting with SR5) |
注意:Spring Cloud 官方已经停止对 Dalston、Edgware、Finchley 和 Greenwich 的版本更新。
查看官网,现在正在支持 CURRENT&GA
的版本是 <font style="color:rgb(51, 51, 51);">2021.0.4</font>
(旧版的叫法都是伦敦地铁站名例如 Hoxton
霍克斯顿,简称 H 版,2020年年底发布的新版,就是以年份开头的)
点击查看 参考文档(Reference Doc) 可以看到版本信息
Spring Cloud 版本: 2021.0.4 Spring Boot 版本: 2.6.11查看详细兼容版本地址
地址:https://start.spring.io/actuator/info
请求地址,对json格式化后可以看出,Spring Cloud 2021.0.4
对应Spring Boot >=2.6.1 and <3.0.0-M1
Spring Cloud Alibaba 版本对账官方参考:
版本说明 · alibaba/spring-cloud-alibaba Wiki
父项目搭建
我们先来了解下父工程是什么意思?
我们通过上方描述,可以得知微服务项目,会有很多个小服务,都是可以独立运行的。那么实际开发中我们通常会创建一个父工程,然后父工程中创建一个个的子模块,然后进行必要的配置,以及引入及处理依赖。(管理依赖很重要)
父项目创建步骤
- New Project - maven工程
- 删除不需要的文件。只保留
pom.xml
- 修改
pom.xml
packaging
标签选择 pom
而不是 jar
或者 war
,因为这是父工程,这个位置一般也不写代码,只是聚合(子)工程或者传递依赖使用
<packaging>pom</packaging>
<?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">
<modelVersion>4.0.0</modelVersion>
<!-- 聚合工程或者传递依赖使用-->
<packaging>pom</packaging>
<groupId>org.chen</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 统一管理依赖版本-->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<junit.version>4.13.1</junit.version>
<mysql.version>8.0.29</mysql.version>
<!-- 下面这两个的兼容问题要注意 -->
<spring.boot.version>2.6.11</spring.boot.version>
<spring.cloud.version>2021.0.4</spring.cloud.version>
</properties>
<!--1. dependencyManagement的作用相当于一个对所依赖jar包进行版本管理的管理器
2. 子模块继承之后,作用:锁定版本 + 子module 不用声明groupId和version -->
<dependencyManagement>
<dependencies>
<!--spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
DependencyManagement 的意义
在父类的 pom 中,你可以观察到,即使你写了这么多的依赖,但是本地却没有加载任何 jar 包,好像就是个摆设,如果单单看父工程的话,这话也没大毛病,但是作为一个拥有多个子模块的父工程来说,它就很有意义了。
Maven 使用 dependencyManagement 元素来提供了一种管理依赖版本号的方式。
通常会在一个组织或者项目的最顶层的父 POM 中看到 dependencyManagement 元素。
使用 pom.xml 中的 dependencyManagement 元素能让所有在子项目中引用依赖而不用显式的列出版本号。Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个 dependencyManagement 元素中指定的版本号。
<dependencyManagement>
<dependencies>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改;另外如果某个子项目需要另外的一个版本,只需要声明version就可。
- 如果不在子项目中声明依赖,不会从父项目中继承下来的。
- 只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且 version 和 scope 都读取自父pom。
- 如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。