初识SpringCloud

时间:2024-10-01 07:05:41

认识微服务

随着互联网行业的蓬勃发展,用户规模和业务复杂度呈指数级增长,对服务的要求也水涨船高。传统的单体架构逐渐暴露出难以扩展、维护困难等弊端,无法满足日益增长的需求。在这种背景下,微服务架构应运而生,并迅速成为主流。

单体架构 vs 微服务架构:

单体架构

微服务架构

为了更好地理解微服务架构的优势,我们需要先了解它与传统单体架构的区别:

特性 单体架构 微服务架构
架构模式 将所有功能模块打包在一个应用程序中 将应用程序拆分为多个独立的服务
部署方式 整体部署,更新迭代影响整个应用 独立部署,每个服务可以独立开发、测试、部署和扩展
扩展性 垂直扩展,受限于单个服务器的性能 水平扩展,可以根据需求独立扩展每个服务
技术栈 统一技术栈,技术选型受限 每个服务可以使用不同的技术栈,灵活性高
故障隔离 单点故障,一个模块出问题会影响整个应用 故障隔离,一个服务出问题不会影响其他服务
开发效率 开发、测试、部署周期长,协作困难 开发、测试、部署周期短,团队协作更高效
运维复杂度 相对简单,但难以应对大规模应用 相对复杂,需要更完善的监控、日志、服务治理等机制

微服务架构的优势:

  • 灵活性高: 每个服务独立开发、部署和扩展,可以根据业务需求选择最适合的技术栈。

  • 可扩展性强: 可以根据需求独立扩展每个服务,避免资源浪费。

  • 故障隔离: 一个服务出问题不会影响其他服务,提高系统稳定性。

  • 开发效率高: 团队可以并行开发不同的服务,缩短开发周期。

  • 易于维护: 每个服务职责单一,代码量少,易于理解和维护。

因此,可以认为微服务是一种经过良好架构设计的分布式架构方案

SpringCloud

SpringCloud是目前国内使用最广泛的微服务框架。官网地址:Spring Cloud

SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。

其中常见的组件包括:

另外,SpringCloud底层是依赖于SpringBoot的,并且有版本的兼容关系,如下:

SpringCloud与SpringBoot的版本的兼容关系

主要功能组件:

  • 服务注册与发现: 例如 Eureka、Consul。
  • 服务调用: 例如 Feign、RestTemplate。
  • 负载均衡: 例如 Ribbon。
  • 服务熔断: 例如 Hystrix。
  • 服务网关: 例如 Zuul、Spring Cloud Gateway。
  • 配置中心: 例如 Spring Cloud Config。
  • 消息总线: 例如 Spring Cloud Bus。
  • 分布式链路追踪: 例如 Sleuth + Zipkin。

总结

  • 单体架构:简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统

  • 分布式架构:松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝

  • 微服务:一种良好的分布式架构方案

    ①优点:拆分粒度更小、服务更独立、耦合度更低

    ②缺点:架构非常复杂,运维、监控、部署难度提高

  • SpringCloud是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件

服务拆分和远程调用

任何分布式架构都离不开服务的拆分,微服务也是一样。

服务拆分原则

在微服务架构中,服务拆分是至关重要的一步。合理的服务拆分能够提高系统的可维护性、可扩展性和灵活性。以下是一些微服务拆分的原则:

1. 单一职责原则 (Single Responsibility Principle, SRP)

  • 原则: 每个微服务应该只负责一个单一的业务功能或业务流程。
  • 解释: 单一职责原则有助于降低服务的复杂性,使得每个服务更加专注于特定的业务领域,从而提高代码的可维护性和可测试性。
  • 示例: 如果一个服务既负责用户管理,又负责订单处理,那么应该将其拆分为两个独立的服务:一个负责用户管理,另一个负责订单处理。

2. 高内聚低耦合 (High Cohesion, Low Coupling)

  • 原则: 微服务内部的功能应该高度内聚,而不同微服务之间的耦合度应该尽量低。
  • 解释: 高内聚意味着服务内部的功能紧密相关,低耦合意味着服务之间的依赖关系尽量少。这样可以降低系统的复杂性,提高服务的独立性和可重用性。
  • 示例: 用户服务和订单服务应该尽量独立,用户服务不应该直接访问订单服务的数据库。

3. 避免重复开发相同业务

  • 原则: 不同微服务之间不要重复开发相同的业务逻辑。
  • 解释: 重复开发会导致代码冗余,增加维护成本,并且容易引发数据一致性问题。可以通过共享库或服务调用来避免重复开发。
  • 示例: 如果多个服务都需要进行用户认证,可以将认证逻辑提取到一个独立的认证服务中,其他服务通过调用该服务来实现认证功能。

4. 数据独立性

  • 原则: 每个微服务应该拥有自己的数据库,并且不应该直接访问其他微服务的数据库。
  • 解释: 数据独立性有助于保证服务的独立性和数据的安全性。如果一个服务需要访问其他服务的数据,应该通过服务接口进行调用,而不是直接访问数据库。
  • 示例: 订单服务不应该直接访问用户服务的数据库,而是通过用户服务提供的接口来获取用户信息。

5. 接口暴露

  • 原则: 微服务可以将自己的业务功能暴露为接口,供其他微服务调用。
  • 解释: 通过接口暴露业务功能,可以实现服务之间的松耦合,并且便于服务的扩展和维护。
  • 示例: 用户服务可以提供一个接口,允许其他服务查询用户信息,订单服务可以通过调用该接口来获取用户信息。

6. 关注点分离 (Separation of Concerns)

  • 原则: 将不同的关注点分离到不同的服务中。
  • 解释: 关注点分离有助于降低系统的复杂性,使得每个服务更加专注于特定的业务领域。
  • 示例: 将业务逻辑、数据访问、消息处理等不同的关注点分离到不同的服务中。

7. 可扩展性

  • 原则: 服务拆分应该考虑到未来的扩展需求。
  • 解释: 服务拆分应该使得系统能够方便地进行水平扩展,以应对未来的业务增长。
  • 示例: 将用户服务拆分为多个子服务,例如用户注册服务、用户认证服务等,以便在未来根据需求独立扩展这些子服务。

8. 团队独立性

  • 原则: 服务拆分应该考虑到团队的独立性。
  • 解释: 每个服务应该由一个独立的团队负责开发和维护,这样可以提高团队的自主性和开发效率。
  • 示例: 将用户服务和订单服务分配给不同的团队,每个团队可以独立开发和部署自己的服务。

提供者与消费者

在微服务架构中,服务调用关系中存在两个不同的角色:服务提供者和服务消费者。这两个角色并不是绝对的,而是相对于具体的业务调用关系而言。

  • 服务提供者:服务提供者是指在一次业务中,被其他微服务调用的服务。它提供接口给其他微服务使用。
  • 服务消费者:服务消费者是指在一次业务中,调用其他微服务的服务。它通过调用其他微服务提供的接口来完成业务逻辑。
  • 角色相对性:服务提供者与服务消费者的角色并不是固定的,而是相对于具体的业务调用关系而言。一个服务在不同的业务调用关系中可能扮演不同的角色。

示例

假设有三个服务:服务A、服务B 和 服务C。

  • 服务A 调用了 服务B。
  • 服务B 调用了 服务C。

在这种情况下:

对于A调用B的业务而言:

  • 服务A 是服务消费者。
  • 服务B 是服务提供者。

对于B调用C的业务而言:

  • 服务B 是服务消费者。
  • 服务C 是服务提供者。

因此,服务B 既可以是服务提供者,也可以是服务消费者,这取决于它所处的业务调用关系。

Ribbon负载均衡

在 Spring Cloud 中,Ribbon 是一个客户端负载均衡器,它可以帮助我们在微服务架构中实现负载均衡。Ribbon 通过与服务注册中心(如 Nacos )集成,自动发现可用的服务实例,并根据负载均衡策略将请求分发到不同的实例上。

Ribbon 通过拦截 RestTemplate 的请求,并根据服务名称从 Nacos 获取服务实例列表,然后利用负载均衡算法选择一个实例,最终将请求发送到选中的实例上。

Spring Cloud Ribbon 的负载均衡机制通过以下步骤实现:

  1. 拦截请求: LoadBalancerInterceptor 拦截 RestTemplate 的请求。
  2. 获取服务名称: 从请求 URI 中提取服务名称。
  3. 获取服务实例列表: ILoadBalancer 从 Eureka 获取服务实例列表。
  4. 选择服务实例: IRule 根据负载均衡策略选择一个服务实例。
  5. 发送请求: 将请求发送到选中的服务实例。

负载均衡策略

负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:

不同规则的含义如下:

内置负载均衡规则类 规则描述
RoundRobinRule 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule 忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule 随机选择一个可用的服务器。
RetryRule 重试机制的选择逻辑

默认的实现就是ZoneAvoidanceRule,是一种轮询方案

自定义负载均衡策略

通过定义IRule实现可以修改负载均衡规则,有两种方式:

代码方式:在 order-service 的启动类中,通过定义一个新的 IRule Bean 来修改负载均衡规则:

@Bean
public IRule randomRule() {
    return new RandomRule();
}

配置文件方式:在 order-service 的 application.yml 文件中,添加新的配置来修改负载均衡规则:

userservice: # 给某个微服务配置负载均衡规则,这里是 userservice 服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则

注意,一般用默认的负载均衡规则,不做修改。

饥饿加载

Ribbon 默认采用懒加载(Lazy Loading),即在第一次访问时才会去创建 LoadBalanceClient,这会导致第一次请求的时间较长。为了降低第一次访问的耗时,可以通过配置开启饥饿加载(Eager Loading),即在项目启动时创建 LoadBalanceClient。

ribbon:
  eager-load:
    enabled: true
    clients: userservice

Nacos注册中心

认识和安装Nacos

Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

Windows安装

开发阶段采用单机安装即可。

下载安装包

在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:

GitHub主页:https://github.com/alibaba/nacos/releases/tag/2.4.2.1

GitHub的Release下载页:https://github.com/alibaba/nacos/releases

如图:

windows版本使用nacos-server-2.4.2.1.zip包即可。

解压

将这个包解压到任意非中文目录下,如图:

目录说明:

  • bin:启动脚本

  • conf:配置文件

端口配置

Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。

如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:

修改其中的内容:

启动

启动非常简单,进入bin目录,结构如下:

然后执行命令即可:

 windows命令: startup.cmd -m standalone

执行后的效果如图:

访问

在浏览器输入地址:http://127.0.0.1:8848/nacos即可:

默认的账号和密码都是nacos,进入后:

服务注册到nacos

Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。

主要差异在于:

  • 依赖不同

  • 服务地址不同

引入依赖

在父工程的pom文件中引入依赖:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cloud</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging> <!-- 这里设置为pom -->
    <name>demo</name>
    <description>demo</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2023.0.1.0</version>
            <type>pom</type>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <!-- 定义子模块 -->
    <modules>
        <module>service1</module>
    </modules>

</project>

service1子工程

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>cloud</groupId>
		<artifactId>demo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
		<relativePath>../pom.xml</relativePath> <!-- 指向父项目的 pom 文件 -->
	</parent>
	<artifactId>service1</artifactId>
	<dependencies>
		<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery -->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
			<version>2023.0.1.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
			<version>2023.0.1.0</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>
</project>

application.properties

# 服务端口
server.port=8081

# 服务名称
spring.application.name=service1

# Nacos 服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

# 启用 Sentinel,并连接 Sentinel Dashboard
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8080
spring.cloud.sentinel.transport.port=8719  # 本地 Sentinel 客户端的监控端口
# 启用 Sentinel
spring.cloud.sentinel.enabled=true

登录nacos管理页面,可以看到微服务信息:

服务分级存储模型

在微服务架构中,服务实例通常分布在不同的地理位置或数据中心,为了提高服务的可用性和性能,通常会将服务实例划分为不同的集群。Nacos 提供了一种服务分级存储模型,允许将服务实例按照集群进行组织和管理。

服务分级存储模型概述
一个服务可以有多个实例,例如 user-service 可以有以下实例:

  • 127.0.0.1:8081
  • 127.0.0.1:8082
  • 127.0.0.1:8083

这些实例可以分布在不同的机房,例如:

  • 127.0.0.1:8081 在上海机房
  • 127.0.0.1:8082 在上海机房
  • 127.0.0.1:8083 在杭州机房

Nacos 将同一机房内的实例划分为一个集群。也就是说,user-service 是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型,如图:

集群优先访问策略

在微服务互相访问时,应该尽可能访问同集群的实例,因为本地访问速度更快。当本集群内不可用时,才访问其他集群。例如: 

配置集群信息

在 Nacos 中,可以通过配置文件或 API 来指定服务的集群信息。以下是一个示例配置:

1. 配置文件方式

在 application.yml 中配置集群信息:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        cluster-name: hangzhou

2. API 方式

通过 Nacos 的 API 注册服务时,可以指定集群信息:

Instance instance = new Instance();
instance.setIp("127.0.0.1");
instance.setPort(8081);
instance.setClusterName("hangzhou");

NamingService namingService = NamingFactory.createNamingService("localhost:8848");
namingService.registerInstance("user-service", instance);

集群优先访问的实现

Nacos 提供了 ZoneAvoidanceRule 负载均衡策略,它会优先选择同集群的服务实例。如果同集群的实例不可用,才会选择其他集群的实例。

配置负载均衡策略

在 application.yml 中配置负载均衡策略:

userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule

权重配置

实际部署中会出现这样的场景:

服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。

但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。

因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。

在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:

在弹出的编辑窗口,修改权重:

注意:如果权重修改为0,则该实例永远不会被访问

Nacos 与 Eureka 的区别

Nacos 和 Eureka 都是常用的服务注册与发现组件,它们在功能和实现上有一些相似之处,但也存在一些显著的区别。

服务实例类型

Nacos 的服务实例分为两种类型:

  • 临时实例:如果实例宕机超过一定时间,会从服务列表中剔除,默认类型。

  • 非临时实例(永久实例):如果实例宕机,不会从服务列表中剔除。

可以通过配置将服务实例设置为非临时实例:

spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false # 设置为非临时实例

 

Nacos 与 Eureka 的共同点

  • 服务注册与发现:都支持服务注册和服务拉取。

  • 健康检测:都支持服务提供者通过心跳方式进行健康检测。

Nacos 与 Eureka 的区别

1. 健康检测方式

Nacos:

  • 临时实例:采用心跳模式,如果心跳不正常,实例会被剔除。
  • 非临时实例:采用主动检测模式,实例宕机不会被剔除。

Eureka:

  • 仅支持心跳模式,实例宕机后会从服务列表中剔除。

2. 服务列表更新机制

  • Nacos:支持服务列表变更的消息推送模式,服务列表更新更及时。
  • Eureka:采用客户端定期拉取服务列表的方式,更新相对滞后。

3. 集群模式

  • Nacos:默认采用 AP 方式(可用性和分区容错性)。当集群中存在非临时实例时,采用 CP 方式(一致性和分区容错性)。
  • Eureka:采用 AP 方式,强调高可用性和分区容错性,但不保证一致性。