SpringCloud与微服务-第3章-服务治理(Nacos/Eureka)

第 3 章 - 服务治理


服务治理可以说是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册与发现。为什么我们在微服务架构中那么需要服务治理模块呢?


在最初开始构建微服务系统的时候可能服务并不多,我们可以通过做一些静态配置来完成服务的调用。比如,有两个服务 A 和 B,其中服务 A 需要调用服务 B 来完成一个业务操作时,为了实现服务 B 的高可用,不论采用服务端负载均衡还是客户端负载均衡,都需要手工维护服务 B 的具体实例清单。


但是随着业务的发展,系统功能越来越复杂,相应的微服务应用也不断增加,我们的静态配置就会变得越来越难以维护。并且面对不断发展的业务,我们的集群规模、服务的位置、服务的命名等都有可能发生变化,如果还是通过手工维护的方式,那么极易发生错误或是命名冲突等问题。同时,对于这类静态内容的维护也必将消耗大量的人力。


为了解决微服务架构中的服务实例维护问题,产生了大量的服务治理框架和产品。这些框架和产品的实现都围绕着服务注册与服务发现机制来完成对微服务应用实例的自动化管理。

目标:


在本章中,您将学习:

  • 构建服务注册中心
  • 服务注册与服务发现

常见方案

Netflix Eureka

Spring Cloud Eureka,它既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用 Java 编写,所以 Eureka 主要适用于通过 Java 实现的分布式系统,或是与 JVM 兼容语言构建的系统。但是,由于 Eureka 服务端的服务治理机制提供了完备的 RESTful API,所以它也支持将非 Java 语言构建的微服务应用纳入 Eureka 的服务治理体系中来。


zookeeper

ZooKeeper 是一个开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。ZooKeeper 的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。


Consult

Consul 是一个服务网格(微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控)解决方案,它是一个分布式的,高度可用的系统,而且开发使用都很简便。它提供了一个功能齐全的控制平面,主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心。


Spring Cloud Alibaba Nacos

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。


方案对比

服务治理的方案不一而论,现将主流的技术框架进行横向比对:


bg fit


从上面的对比可以知道,Nacos 作为服务发现中心具备更多的功能支持,且从长远来看 Nacos 在以后的版本会支持 SpringCloud+K8s 的组合,填补两者的鸿沟,在两套体系下可以采用同一套服务发现和配置管理的解决方案,这将大大的简化使用和维护成本.本节课程将以 Nacos 为例,讲解服务治理的相关概念.


服务治理

在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。


搭建 Nacos 服务

Nacos 的安装和使用都非常简单.下面我们就在 Windows 系统上搭建一个 Nacos 服务吧.


1.下载安装包

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

GitHub 主页:https://github.com/alibaba/nacos

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


如图所示: Windows 系统下载对应的 zip 压缩包即可.

image-20220715095333038


2.解压

将 nacos-server-2.1.0.zip 解压到任意非中文目录,此处将其解压到 D 盘的 develop 目录下:

image-20220715104229949


目录说明:

  • bin:启动脚本
  • conf:配置文件

3.修改端口

Nacos 的默认端口是 8848,如果你电脑上的其它进程占用了 8848 端口,请先尝试关闭该进程。如果无法关闭占用 8848 端口的进程,也可以进入 nacos 的 conf 目录,修改配置文件中的端口:


image-20220715104440506


修改其中的内容:

image-20220715104601567


4.启动

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

image-20220715104737161


然后执行命令即可:

当前目录下,打开 cmd 黑窗口,执行 windows 命令:

startup.cmd -m standalone

(以单实例本机启动)。

也可以打开 startup.cmd, 修改
set MODE="clusterset MODE="standalone",然后执行startup.cmd


启动成功界面如下:

image-20220715105459284


5.访问

在浏览器中访问 Console 给出的 URL: http://192.168.1.4:8848/nacos/index.html ,Nacos 默认的账号密码都是 nacos 。


image-20220715105806185


登录成功:

image-20220715105710888


本章节,我们通过两个简单的微服务模块来了解 Nacos 是如何进行服务注册和发现.

  • 服务提供者
  • 服务消费者

注册服务提供者

现在我们已经成功构建了两个微服务业务模块,接下来我们尝试将这两个业务模块加入 Nacos 的服务治理体系中去.服务之间通过 HTTP 协议进行通信,所以当我们使用 Nacos 进行管理的时候,Nacos 也需要知道相关的信息,比如 IP,端口号等。


1. 添加依赖

由于 Spring Cloud Alibaba 是新加入 Spring Cloud 的组件,所以没有在 spring-cloud-dependencies 里面,我们需要在项目的父工程 pom.xml 文件中加入如下依赖管理:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

2. 添加 Nacos 的客户端依赖

在上一节,我们已经搭建了 Nacos 服务,而想将我们的应用注册到 Nacos 中,我们还需要在模块中添加 Nacos 的客户端依赖。现在将依赖添加到其中一个模块的 pom.xml 文件中:

<!-- nacos客户端依赖包 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3. 添加 Nacos 地址

引入以上依赖之后,我们就可以在 application.yml 文件中配置相关信息,将服务注册到 Nacos 中。

在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。


所以我们需要配置本服务的服务名以及 nacos 的服务端地址信息,如下:

spring:
  application:
    name: orderservice #注册到注册中心的服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 #nacos 服务端地址

4. 启动测试

重启当前应用,观察 nacos 控制台服务管理下的服务列表菜单,注册成功则会将该应用实例显示在列表中。


bg fit


按照以上步骤,我们可以将另一个服务也注册到 Nacos 中,注册成功后,可以在 Nacos 中看到两条服务信息.


bg fit


服务发现与消费

通过上面的内容介绍与实践,己经搭建起了微服务架构中的核心组件——服务注册中心。同时,还对两个业务模块做了改造。通过简单的配置,使该程序注册到 Nacos 注册中心上,成为该服务治理体系下的服务。

现在我们己经有了服务注册中心和服务提供者,服务消费者,下面就来尝试让服务消费者完成两个目标,发现服务以及消费服务。我们通过构建一个简单的示例,看看在 Nacos 的服务治理体系下如何实现服务的发现与消费。


1.添加@LoadBalanced 注解

在没有将服务交由 Nacos 管理之前,我们通过简单的 RestTemplate 对象的方法即可模拟 HTTP 请求,而现在我们将使用服务名代替传统的 IP+端口号的这种 URL,并且实现简单的负载均衡(负载均衡的内容我们将在之后的章节详细介绍).


在声明 RestTemplate 的地方添加注解:

/**
     * 在容器中注册 RestTemplate 方便项目内调用
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

2.使用服务名

在发送请求的时候,使用服务名替换掉原 IP+端口。服务消费方 Controller 关键代码如下所示:


@RestController
@RequestMapping("order")
public class OrderController {
   @Autowired
   private OrderService orderService;
   @Autowired
   private RestTemplate restTemplate;
    @GetMapping("{orderId}")
    public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
        // 根据id查询订单并返回
        Order order = orderService.queryOrderById(orderId);
        //通过RestTemplate查询用户信息 使用服务名代替ip和端口号
        String url = "http://userservice/user/"+order.getUserId();
        User forObject = restTemplate.getForObject(url, User.class);
        //封装返回的数据
        order.setUser(forObject);
        return order;
    }
}

3.测试

当以上步骤完成之后,我们就构建了一个简单但完整的
服务消费方->注册中心->服务提供方
的这样一个简单体系。当我们使用浏览器访问一个服务的时候,该服务内部如何去调用其他服务能力接口的整个过程我们是感知不到的。所以我们像以前一样正常访问即可.


如果测试通过,我们将得到包含两个模块数据的结果集,如下所示案例:

image-20220715163624909


如果希望查询到的 User 实例包含配置文件中的端口信息, 可以这样修改 userservcie 模块 中的 UserController 类:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id) {
        User user = userService.queryById(id);
        user.setServerPort(serverPort);
        return user;
    }
}

4.如何启动多个服务提供方

在上面的案例中,我们只启动了一个服务提供方,如果我们想要启动多个服务提供方,并且让服务消费方能够负载均衡的访问到这些服务提供方,那么我们需要做一些额外的配置.


  1. 首先修改运行配置, 开启允许并行运行

bg auto


  1. 准备多个配置文件,并且修改端口号使其各互不冲突, 然后修改运行配置的 active profile 为不同的配置方案, 启动多个服务:

bg cover


  1. 也可以
    直接修改运行配置中的 VM options,添加 -Dserver.port=XXXX,
    其中 XXXX 为不同的端口号, 分别启动多个服务.

bg cover


  1. 找到现有的运行配置服务, 选择复制配置后修改配置文件或者端口设置, 通过不同的运行配置来启动多个服务.

h:14em


  1. 当同时运行 2 个或者多个 springboot 服务时, 可以alt+8打开服务列表, 添加 SpringBoot 的配置类型, 可以方便的统一管理多个服务.


  1. 在 Nacos 运行的运行的情况下可以查看到服务列表, 包括服务的名称, 实例数量, 以及服务的运行状态.
    h:17em

Q: Nacos 服务注册中心的作用是什么?

A: 服务注册中心是一个服务的注册表, 用于存储服务提供方的信息, 以便服务消费方能够找到服务提供方, 从而实现服务的调用.


w:28em


搭建 Eureka 服务

pom.xml

<!--eureka服务端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

application.yml

server:
  port: 10086 # 服务端口
#将Eureka服务注册到Eureka注册中心上,方便以后做集群管理
spring:
  application:
    name: eurekaserver # eureka的服务名称
eureka:
  client:
    service-url: # eureka的地址信息, 如果搭建Eureka集群则需要配置多个地址, 以逗号分隔, 交叉注册
      defaultZone: http://127.0.0.1:10086/eureka

运行 Eureka 服务, 查看可视化界面

h:16em


启用 Eureka 客户端

user 和 order 模块:

@MapperScan("com.niit.user.mapper")
@SpringBootApplication
@EnableEurekaClient  // for uereka
public class MainApplication {
...
}

application.yml

# Eureka 的地址信息
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

Q: Nacos 的服务注册中心和 Eureka 的服务注册中心有什么区别?

A: Nacos 的服务注册中心和 Eureka 的服务注册中心都是用于存储服务提供方的信息, 但是 Nacos 的服务注册中心是一个更加完善的服务注册中心, 它不仅仅是一个服务注册中心, 还是一个配置中心, 服务发现中心, 以及服务管理中心.


活动 3.1

使用 Nacos 实现服务调用


练习问题

  1. 下面哪个选项不是常见的服务治理方案?
    a. Netflix Eureka
    b. Zookeeper
    c. Spring Cloud Alibaba Nacos
    d. Spring Boot Application

    答案

    d


  1. 我们在使用 Spring Cloud Alibaba Nacos 作为注册中心,可以在发送请求的时候,使用服务名替换掉原 IP+端口。
    a. 错误
    b. 正确

    答案

    b


  1. 下面哪条命令是在本机以单实例启动 Nacos 服务?
    a. startup.cmd -m standalone
    b. startup.cmd -f standalone
    c. startup.exe -m standalone
    d. startup.cmd

    答案

    a


  1. 下面哪个是 Nacos 注册中心的客户端依赖?
    a. spring-cloud-starter-alibaba-nacos-config
    b. spring-cloud-starter-alibaba-nacos-discovery
    c. spring-cloud-alibaba-starter-nacos-discovery
    d. spring-cloud-alibaba-nacos-starter-discovery

    答案

    b


小结

在本章中,您学习了:

  • 服务治理的常见方案
  • 搭建 Nacos 服务
    • 注册服务提供者
    • 服务发现与消费

Views: 25

SpringCloud与微服务-第2章-SpringBoot基础回顾

第 2 章 - SpringBoot 快速入门

在展开 Spring Cloud 的微服务架构部署之前,我们先通过本章的内容来了解一下用于构建微服务的基础框架——Spring Boot


在本章中我们将介绍下面这些与后续课程有密切联系的内容。

目标:

  • 如何构建 Spring Boot 项目
  • 如何实现 RESTful API 接口
  • 如何实现多环境的 Spring Boot 应用配置
  • Spring Boot 应用的监控与管理

Spring Boot 框架简介

Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序,可以“直接运行”。Spring 平台可以轻松集成常用的第三方库。大多数 Spring Boot 应用程序只需要最少的 Spring 配置。

特征

  • 创建独立的 Spring 应用程序
  • 直接嵌入 Tomcat、Jetty 或 Undertow(无需部署 WAR 文件)
  • 提供“starter”依赖项以简化您的构建配置
  • 尽可能自动配置 Spring 和 第三方方库
  • 提供为生产环境准备的功能,例如指标、健康检查和外部配置
  • 绝对没有代码生成,也不需要 XML 配置

项目构建与解析

系统及工具版本要求

  • Java 8 及以上版本(这里选择 jdk11)
  • IntelliJ IDEA 2020 及以上版本

使用 IDEA 快速创建应用

1.打开新建项目选项卡

选中 Spring Initializr 功能,选择项目 SDK(此处以 JDK 11 为例),最后点击下一步。

bg right fit


2.填写版本信息和项目信息

image-20240413220440276


3.选择相关初始化依赖

image-20240413220511660


4.选择项目的本地路径

image-20240413220535228


工程结构解析

  • src/main/java:源代码目录,该目录用来存放应用的源代码,其中 com.example.demo 包名是由我们在创建项目时指定的。
  • src/main/resources:资源文件目录,该目录用来存放应用的配置文件,如 application.properties、application.yml 等。
  • src/test:测试代码目录,该目录用来存放应用的测试代码。

Maven 配置分析

打开当前工程下的 pom.xml 文件,看看生成的项目都引入了哪些依赖来构建 Spring Boot 工程,内容大致如下所示。

parent:
  - spring-boot-starter-parent@2.7.1
dependencies:
  - spring-boot-starter-web
  - spring-boot-starter-jdbc
  - mysql-connector-java(runtime)@5.1.47
  - mybatis-spring-boot-starter
  - spring-boot-devtools
  - spring-boot-configuration-processor
  - lombok(optional)
  - spring-boot-starter-test
  - spring-data-commons(分页支持)

在基础信息部分,groupld 和 artifactld 对应生成项目时页面上输入的内容。另外 Spring Boot 默认将该微服务应用打包为 jar 的形式,而非 war 的形式,因为默认的 Web 模块依赖会包含嵌入式的 Tomcat,这样使得我们的应用 jar 自身就具备了提供 Web 服务的能力,后续我们会演示如何启动它。


父项目 parent 配置指定为 spring-boot-starter-parent 的 2.7.1 版本,该父项目中定义了 Spring Boot 版本的基础依赖以及一些默认配置内容,比如,配置文件 application.properties 的位置等。在项目依赖 dependencies 配置中,包含了下面两项。

  • spring-boot-starter-web:全桟 Web 开发模块,包含嵌入式 Tomcat、Spring MVC。
  • spring-boot-starter-test:通用测试模块,包含 JUnit、Hamcrest、Mockito。

这里所引用的 web 和 test 模块,在 Spring Boot 生态中被称为 Starter POMs。Starter POMs 是一系列轻便的依赖包,是一套一站式的 Spring
相关技术的解决方案。开发者在使用和整合模块时,不必再去搜寻样例代码中的依赖配置来复制使用,只需要引入对应的模块包即可。


Spring Boot 的 官方 Starter POMs 采用 spring-boot-starter-xxx的命名方式,*代表一个特别的应用功能模块,比如这里的 web、test。

另外 xxx.sring-boot-starter 命名的模块是非官方的第三方模块,比如 mybatis-spring-boot-starter


文档:
https://docs.spring.io/spring-boot/docs/2.3.12.RELEASE/reference/html/index.html


实现 RESTful API

在 Spring Boot 中创建一个 RESTful API 的实现代码同 Spring MVC 应用一样,只是不需要像 Spring MVC 那样先做很多配置,而是像下面这样直接开始编写 Controller 内容:

  • 新建 package,可根据实际的构建情况修改成自己的路径。
  • 新建 HelloWorldController 类,内容如下所示。

package com.niit.quickstart.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 控制器类
 */
@RestController
@RequestMapping("/test")
public class HelloWorldController {
    @RequestMapping("/hello")
    public String showHello(){
        return "Hello World!";
    }
}

  • 找到启动类,启动该应用

image-20220711233735883


  • 启动成功,默认端口 8080

    image-20220711233826742


image-20220711233007071


  • 在服务器上部署运行时,通常先使用 mvn package 或 mvn install(双击即可) 将应用打包成 jar 包,再通过 java -jar xxx.jar 来启动应用。

image-20220711233958684


编写单元测试

功能实现之后,我们要养成随手写配套单元测试的习惯,这在微服务架构中尤为重要。

单元测试是在开发过程中用来验证代码正确性非常好的手段,并且这些单元测试将会很好地支持我们未来可能会进行的重构。


在 Spring Boot 中实现单元测试同样非常方便,下面我们编写一个简单的单元测试来模拟 HTTP 请求,测试之前实现的/hello 接口,该接口应返回 Hello World 字符串。

该单元测试使用 RestTemplate 类模拟 发出 HTTP 请求.


1. 声明 RestTemplate 类

/**
 *主配置类 项目启动入口
 */
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    /**
     * 在bean容器中注册RestTemplate 方便在应用中使用
     * @return
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

2.编写单元测试类


/**
 * @apiNote 测试类 用于测试controller 通过restTemplate调用接口 (需要启动项目)
 */
@SpringBootTest
class TestControllerTest {

    @Autowired
    RestTemplate restTemplate;

    @Test
    public void test() {
        String url = "http://localhost:8080/test/hello";
        String result = restTemplate.getForObject(url, String.class);
        System.out.println(result);
    }
}

3.点击测试方法左侧按钮,开始测试

image-20220711235420676


4.测试结果

image-20220711235510407


5.注意事项

使用 RestTemplate 进行访问时,需要确保服务端保持活动状态,否则会测试不通过.

image-20220711235804341


快速入门小结

我们通过 IDEA 工具构建了一个 Spring Boot 的基础项目,并详细介绍了该基础项目的结构及 Maven 的依赖关系,接着在该项目中实现一个输出“Hello World”的 RESTful 接口以及针对该接口的单元测试用例,完成了一个虽然简单,但涵盖了项目构建、服务开发、单元测试的全套开发内容。


我们在后续使用 Spring Cloud 的时候会构建多个 Spring Boot 的项目来实现微服务架构中的基础设施以及微服务应用,通过本节的学习,我们己经具备了使用 Spring Boot 来构建简单微服务项目的基本能力。我们将对后续在 Spring Cloud 组件使用过程中会涉及的配置内容做一些介绍和解释。


配置详解

我们已经轻松地实现了一个简单的 RESTful API 应用,体验了 Spring Boot 的诸多优点。在配置方面,除了 Maven 的配置之外,没有引入任何其他配置。这就是之前我们提到的,Spring Boot 针对常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化配置内容。


但是,我们还是需要了解如何在 Spring Boot 中修改这些自动化的配置内容,以应对一些特殊的场景需求。比如,我们在同一台主机上需要启动多个基于 Spring Boot 的 Web 应用,若不为每个应用指定特别的端口号,那么默认的 8080 端口必将导致冲突。


配置文件

我们在使用 Spring Cloud 的各个组件的时候,其实有大量的工作都会是针对配置文件的。所以我们有必要深入了解一些关于 Spring Boot 中的配置文件的知识,比如配置方式、如何实现多环境配置、配置信息的加载顺序等


Spring Boot 的工程结构 src/main/ resources 目录是 Spring Boot 的配置目录,所以当要为应用创建个性化配置时,应在该目录下进行。Spring Boot 的默认配置文件位置为 src/main/resources/application.properties

image-20220712110030957

上图在配置文件中定义了应用的端口号以及上下文路径.


关于 Spring Boot 应用的配置内容都可以集中在该文件中,根据我们引入的不同 Starter 模块,可以在这里定义容器端口号、数据库连接信息、日志级别等各种配置信息。Spring Boot 的配置文件除了可以使用传统的 properties 文件之外,还支持现在被广泛推荐使用的
YAML 文件。

image-20220712110332417


通过 YAML 的配置方式我们可以看到,配置信息利用阶梯化缩进的方式,其结构更为清晰易读,同时配置内容的字符量也得到显著减少。后续的配置文件,我们将逐步使用 YAML 方式进行编写.


YAML(英语发音为/;jaem3l/,尾音类似 camel 骆驼)是一个可读性高,用来表达资料序列的格式。YAML 的意思其实是:Yet Another Markup Language (仍是一种标记语言),但为了强调这种语言以数据作为中心,而不是以标记语言为重点,而用反向缩略语重新命名。

YAML 的语法特别适合用来表达或编辑数据结构、各种设定文档、文件大纲(例如,许多电子邮件标题格式和 YAML 非常接近)。尽管它比较适合表达阶层式(hierarchical model)的数据结构,不过也有精致的语法可以表示关联性(relational model)的数据结构。

由于 YAML 使用空白符号和分行来分隔资料,其让人最容易上手的特色是巧妙避开各种封闭符号,如引号、各种括号等,这些符号在 xml 结构时会变得复杂而难以辨认。


自定义参数

除了可以在 Spring Boot 的配置文件中设置各个 Starter 模块中预定义的配置属性,也可以在配置文件中定义一些我们需要的自定义属性。比如在 application.yml 中添加:

# 自定义属性
book:
  name: SpringCloudInAction
  author: Jeff

自定义 Book 类,使用@Value 进行属性注入

@Component
@Data
public class Book {
    @Value("${book.name}")
    private String name;
    @Value("${book.author}")
    private String author;
}

@Value 注解加载属性值时可以支持两种表达式来进行配置,如下所示。

  • 一种是上面介绍的 PlaceHolder 方式,格式为${ • • • },大括号内为 PlaceHolder。
  • 另一种是使用 SpEL 表达式(Spring Expression Language),格式为#{•••},大括号内为 SpEL 表达式

编写单元测试

/**
 * Book属性注入 单元测试类
 */
@SpringBootTest
class BookTest {
    @Autowired
    Book book;

    @Test
    public void testBook() {
        System.out.println(book);
    }

}

启动测试,测试结果将在控制台输出:

image-20220712112643626


参数引用

在 application .yml 中的各个参数之间可以直接通过使用 PlaceHolder 的方式来进行引用,就像下面的设置 :

# 自定义属性
book:
  name: SpringCloudInAction
  author: Jeff
  desc: ${book.author} is writing <<${book.name}>>

修改 Book 类,添加 desc 属性.

@Component
@Data
public class Book {
    @Value("${book.name}")
    private String name;
    @Value("${book.author}")
    private String author;
    @Value("${book.desc}")
    private String desc;
}

单元测试步骤同上.测试成功将在控制台打印:

image-20220712114324986


多环境配置

我们在开发应用的时候,通常同一套程序会被应用和安装到几个不同的环境中,比如开发、测试、生产等。其中每个环境的数据库地址、服务器端口等配置都不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常烦琐且容易发生错误的事。

Spring Boot 通过配置多份不同环境的配置文件,实现多环境配置更加简单.


在 Spring Boot 中,多环境配置的文件名需要满足 application-{profile}.yml 的格式(我们以.yml 格式为例),其中{profile}对应你的环境标识,如下所示:

  • application-dev.yml:开发环境。
  • application-test.yml:测试环境。
  • application-prod.yml:生产环境。

至于具体哪个配置文件会被加载,需要在 application.yml 文件中通过 spring.profiles.active 属性来设置,其值对应配置文件中的{profile}值。如 spring.profile.active=dev 就会加载 application-dev.yml 配置文件内容。


如下所示:

image-20220712122558581


多环境的配置思路:

  • application.yml 中配置通用内容,并设置 spring.profiles.active=dev,以开发环境为默认配置。
  • application-{profile}.yml 中配置各个环境不同的内容。

加载顺序

在上面的例子中,我们将 SpringBoot 应用需要的配置内容都放在了项目工程中,己经能够通过 spring.profiles.active 来实现多环境的支持。但是,当团队逐渐壮大,分工越来越细致之后,往往不需要让开发人员知道测试或是生产环境的细节,而是希望由每个环境各自的负责人(QA 或是运维)来集中维护这些信息。


那么如果还是以这样的方式存储配置内容,对于不同环境配置的修改就不得不去获取工程内容来修改这些配置内容,当应用非常多的时候就变得非常不方便。同时,配置内容对开发人员都可见,这本身也是一种安全隐患。对此,出现了很多将配置内容外部化的框架和工具,后续将要介绍的 Spring Cloud Config 和Spring Cloud Alibaba Nacos就是一些解决方案,为了后续能更好地理解 这些解决方案 的加载机制,我们需要对 Spring Boot 对数据文件的加载机制有一定的了解。


为了能够更合理地重写各属性的值,Spring Boot 使用了下面这种较为特别的属性加载顺序:

  1. 在命令行中传入的参数。
  2. SPRING_APPLICATION_JSON 中的属性。SPRING_APPLICATION_JSON 是以 JSON 格式配置在系统环境变量中的内容。
  3. java:comp/env 中的 JNDI 属性。
  4. Java 的系统属性,可以通过 System.getProperties()获得的内容。
  5. 操作系统的环境变量。
  6. 通过 random.* 配置的随机属性。

优先级按上面的顺序由高到低,数字越小优先级越高。


  1. 位于当前应用 jar 包之,针对不同{profile}环境的配置文件内容,例如 application-{profile}.properties 或是 YAML 定义的配置文件。
  2. 位于当前应用 jar 包之,针对不同{profile}环境的配置文件内容,例如 application-{profile}.properties 或是 YAML 定义的配置文件。
  3. 位于当前应用 jar 包之外的 application.properties 和 YAML 配置内容。

可以看到,其中第 7 项和第 9 项都是从应用 jar 包之外读取配置文件,所以,实现外部化配置的原理就是从此切入,为其指定外部配置文件的加载位置来取代 jar 包之内的配置内容。通过这样的实现,我们的工程在配置中就变得非常干净,只需在本地放置幵发需要的配置即可,而不用关心其他环境的配置,由其对应环境的负责人去维护即可。


  1. 位于当前应用 jar 包之内的 application.properties 和 YAML 配置内容。
  2. @Configuration 注解修改的类中,通过 @PropertySource 注解定义的属性。
  3. 应用默认属性,使用 SpringApplication.setDefaultProperties 定义的内容。

配置加载优先级通用原理总结:

  • 距离用户越近,优先级越高,距离程序越近,优先级越低。
  • 个性化配置优先级高于通用化配置

活动 2.1

创建 SpringBoot 应用,使用 RestTemplate 进行互相调用。


监控与管理

在微服务架构中各个应用的内部逻辑因分解而得以简化,但是由于部署应用的数量成倍增长,使得系统的维护复杂度大大提升。对于运维人员来说,为了能对这些成倍增长的应用做到高效运维,传统的运维方式显然是不合适的,所以我们需要实现一套自动化的监控运维机制,而这套机制的运行基础就是不间断地收集各个微服务应用的各项指标情况,并根据这些基础指标信息来制定监控和预警规则,更进一步甚至做到一些自动化的运维操作等。


当我们决定用 Spring Boot 来作为微服务框架时,除了它强大的快速开发功能之外,还因为它在 Starter POMs 中提供了一个特殊依赖模块 spring-boot-starter-actuator

引入该模块能够自动为 SpringBoot 构建的应用提供一系列用于监控的端点。当然,它也并不是万能的,有时候也需要对其做一些简单的扩展来帮助我们实现自身系统个性化的监控需求。所以,在本节将详细介绍一些关于 spring-boot-starter-actuator 模块的内容,包括原生提供的端点以及一些常用的扩展和配置方式等。


初识 actuator

下面,我们通过对“快速入门”小节中实现的 Spring Boot 应用增加 spring-boot- starter-actuator 模块功能,来对它有一个直观的认识。在现有的 Spring Boot 应用中引入该模块非常简单,只需要在 pom. xml 的 dependency 节点中,新增 spring-boot-starter-actuator 的依赖即可,具体如下:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

增加该依赖之后,重新启动应用。此时输出:

image-20220712193352403

上图显示了端点定义,这个端点并非我们自己在程序中创建的,而是由 spring-boot-starter-actuator 模块根据应用依赖和配置自动创建出来的监控和管理端点。通过这个端点,我们可以实时获取应用的各项监控指标


比如访问/actuator 端点。我们可以获得如下类似信息:

{
  "_links": {
    "self": {
      "href": "http://localhost:8081/dev/actuator",
      "templated": false
    },
    "health": {
      "href": "http://localhost:8081/dev/actuator/health",
      "templated": false
    },
    "health-path": {
      "href": "http://localhost:8081/dev/actuator/health/{*path}",
      "templated": true
    }
  }
}

在没有引入其他依赖之前,该端点的内容较为简单,后续我们在使用 Spring Cloud 的各个组件之后,它的返回会变得非常丰富,这些内容将帮助我们制定更为个性化的监控策略。


原生端点

通过在快速入门示例中添加 spring-boot-starter-actuator 模块,我们已经对它有了一个初步的认识。接下来,我们详细介绍一下 spring-boot-starter-actuator 模块中己经实现的一些原生端点。根据端点的作用,可以将原生端点分为以下三大类。

  • 应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与 SpringBoot 应用密切相关的配置类信息。
  • 度量指标类:获取应用程序运行过程中用于监控的度量指标,比如内存信息、线程池信息、HTTP 请求统计等。
  • 操作控制类:提供了对应用的关闭等操作类功能。

出于安全考虑,某些端点默认是没有对外暴露的.为方便测试,我们进行手动开启.

在 application.yml 中添加如下配置:

#主动暴露所有web端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

下面我们来详细了解一下这三类端点都分别可以为我们提供怎样的有用信息和强大功能,以及我们如何去扩展和配置它们。


应用配置类

由于 Spring Boot 为了改善传统 Spring 应用繁杂的配置内容,采用了包扫描和自动化配置的机制来加载原本集中于 XML 文件中的各项内容。虽然这样的做法让我们的代码变得非常简洁,但是整个应用的实例创建和依赖关系等信息都被离散到了各个配置类的注解上,这使我们分析整个应用中资源和实例的各种关系变得非常困难。而这类端点可以帮助我们轻松获取一系列关于 Spring 应用配置内容的详细报告,比如 Bean 创建的报告、环境属性的报告等。


/beans

该端点用来获取应用上下文中创建的所有 Bean。以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/beans ,可获取如下信息(部分展示):

image-20220712210434708


在如上示例中,我们可以看到在每个 Bean 中都包含了下面这些信息 。

  • scope:Bean 的作用域。
  • type:Bean 的 Java 类型。
  • resource:class 文件的具体路径。
  • dependencies:依赖的 Bean 名称 。

/configprops:

以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/configprops 。该端点用来获取应用中配置的属性信息报告。如下所示,prefix 属性代表了属性的配置前缀,properties 代表了各个属性的名称和值。

image-20220712212525622


所以,我们可以通过该报告来看到各个属性的配置路径,比如我们要关闭该端点,就可以通过使用 endpoints.configprops.enabled=false 来完成设置。如下所示,添加该配置后重启应用即可关闭 /configprops 端点

management:
  endpoint:
    configprops:
      enabled: false

/env:

以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/env。该端点与/configprops 不同,它用来获取应用所有可用的环境属性报告,其中还包括了应用还没有使用的配置,所以它可以帮助我们方便地看到当前应用可以加载的配置信息,并配合@ConfigurationProperties 注解将它们引入到我们的应用程序中来进行使用。另外,为了配置属性的安全,对于一些类似密码等敏感信息,该端点都会进行隐私保护,但是我们需要让属性名中包含 password、secret、key 这些关键词,这样该端点在返回它们的时候会使用 * 来替代实际的属性值。


image-20220712213122940


/mappings:

以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/mappings。该端点用来返回所有 Spring MVC 的控制器映射关系报告。

image-20220712213559520


度量指标类

上面我们所介绍的应用配置类端点可以说是一个静态报告。而度量指标类端点提供的报告内容则是动态变化的,这些端点提供了应用程序在运行过程中的一些快照信息,比如内存使用情况、HTTP 请求统计、外部资源指标等。

这些端点对于我们构建微服务架构中的监控系统非常有帮助,由于 Spring Boot 应用自身实现了这些端点,所以我们可以很方便地利用它们来收集我们想要的信息,以制定出各种自动化策略。下面,我们就来分别看看这些强大的端点功能。


/metrics:

以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/metrics

该端点用来返回当前应用的各类重要度量指标,比如内存信息、线程信息、垃圾回收信息等。


如下所示(部分):

image-20220713112701319


在当前路径后拼接上面路径即可查看对应详细信息. 如拼接"disk.free" ,访问: http://localhost:8081/dev/actuator/metrics/disk.free

可以获知当前磁盘剩余可用容量。

image-20220713113557094


/health:

以本项目 dev 环境为例,启动后访问: http://localhost:8081/dev/actuator/health。该端点用来获取应用的各类健康指标信息。如下所示:

image-20220713115753613


当前项目较为简单,只标识出应用的状态.后续集成 Spring Cloud 组件后,返回信息将变得丰富起来.状态码解释如下:

  • UNKNOWN:未知状态,映射 HTTP 状态码为 503
  • UP:正常,映射 HTTP 状态码为 200
  • DOWN:失败,映射 HTTP 状态码为 503
  • OUT_OF_SERVICE:不能对外提供服务,但是服务正常。映射 HTTP 状态码为 200

操作控制类

操作控制类端点拥有更强大的控制能力,如果要使用它们的话,需要通过属性来配置开启操作。

在原生端点中,只提供了一个用来关闭应用的端点:/shutdown(在后续我们引入了 Eureka 之后,会引入更多控制端点)。可以通过如下配置开启它:

image-20220713122012395


在配置了上述属性之后,只需要访问该应用的/shutdown 端点就能实现关闭该应用的远程操作。由于开放关闭应用的操作本身是一件非常危险的事,所以真正在线上使用的时候,需要对其加入一定的保护机制,比如定制 actuator 的端点路径、整合 Spring Security 进行安全校验等。

该端点不支持 Get 请求,我们可以借助 PostMan 等类似工具发送 POST 请求.

image-20220713121939085


活动 2.2


在 Spring Boot 项目中通过 actuator 端点查看应用的相关信息.

小问题 :

以下哪个内部类在定义时可以没有类名称?

  • A. 正则内部类
  • B. 静态内部类
  • C. 方法 - 局部内部类
  • D. 匿名内部类
答案

D


启用热部署以提升开发效率

IDEA 2021.2 版本 DevTools 热部署为例

首先引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

修改 Settings 里 Compiler 选项下的配置,将下图中红色方框里的选项全部勾上

bg


修改 Advanced Settings 里的配置,将下图的选项勾上


注意 Advanced Settings 选项只有在 File->Settings 里面才有,而在 File->New Projects Setup->Settings for New Projects 这个设置页面里是没有的,所以只能设置本项目,而不能自动设置新建的项目。

如果使用的是 IDEA 2021.2 之前版本的话还是使用快捷键 shift+Ctrl+Alt+/,选择 Registry…,将 compiler.automake.allow.when.app.running 选项勾上。

完成上面的步骤后重启下 IDEA 基本就没问题了,如果还是不行的话建议检查 pom 和 application 文件是否引入了 DevTools 依赖并且正确配置。


练习问题


  1. 下面哪个选项不是 Spring Boot 的基础结构?
  • a. src/main/java
  • b. src/main/resources
  • c. src/test:
  • d. src/main/spring
答案

d


  1. 我们在使用 Restful 风格进行编程的时候,是遵循了配置大于约定的规范。
  • a. 错误
  • b. 正确
答案

a


  1. 下面哪个不是 Spring Boot 应用的配置文件?
  • a. application.yml
  • b. application.yaml
  • c. userservice.xml
  • d.application.properties
答案

c


  1. 在 Spring Boot 应用中,不必引入 spring-boot-starter-actuator 的 Maven 即可实现端点监控?

    a. 正确
    b. 错误

    答案

    b


小结

  • 本章我们通过构建一个最基本的 Spring Boot 工程,让大家对其有了一个最直观的感受。
  • 为后续构建各类 Spring Cloud 组件和微服务应用做了一些基础准备工作。
  • 另外,我们对 SpringBoot 中的配置原理以及监控管理做了深入的介绍,因为这些内容将在后续的介绍中有所涉及,并且它们有助于理解 Spring Cloud 组件的运行原理。
  • 更多关于使用 Spring Boot 开发微服务应用的内容,我们不在本书中详细介绍,读者可参阅官方文档或其他书籍资料来学习

Views: 55

SpringCloud与微服务-第1章-基本概念简介

第 1 章 - 微服务基础知识

在进行 SpringCloud 的具体内容介绍之前,我们先通过本章学习一些关于微服务架构以及 SpringCloud 的基础知识。对 SpringCloud 能够解决的具体问题有一个大致的了解,以帮助我们更好地理解后续章节对各个组件的介绍。


学习目标

  • 了解分布式系统
  • 了解什么是微服务架构
  • 为什么选择 Spring Cloud

随着业务规模和系统复杂度的提升,很多系统会历经从单一架构到垂直架构,再到分布式架构的技术发展过程。


单体系统

在以往传统的企业系统架构中,我们针对一个复杂的业务需求通常使用对象或业务类型来构建一个单体项目。在项目中我们通常将需求分为三个主要部分:数据库、服务端处理、前端展现。在业务发展初期,由于所有的业务逻辑在一个应用中,开发、测试、部署都还比较容易且方便。


w:36em



但是由于单体系统部署在一个进程内,功能之间是紧密耦合的, 往往我们修改了一个很小的功能,部署上线会影响其他功能的运行。

并且,单体应用中的这些功能模块的使用场景、并发量、消耗的资源类型都各有不同,对于资源的利用又互相影响,这样使得我们对各个业务模块的系统容量很难给出较为准确的评估。

所以,单体系统在初期虽然可以非常方便地进行开发和使用,但是随着系统的发展,维护成本会变得越来越大,且难以控制。


面对大流量高并发的用户访问,以及随之产生的海量数据处理等诸多挑战下,如何能为用户提供稳定可靠的服务,成为目前很多互联网大公司面临的技术问题。比如,常见的高并发场景有:

  • 购物节
  • 春运购票
  • 秒杀系统
  • 抖音

购物节的支付系统要想要在高并发的场景下实现五个 9(99.999%)的高可用性,保证支付率,还要保证秒杀场景下单位时间内成交的订单数更多,仅靠单一架构是不能做到的。


如果采用集群的垂直架构,随着业务的发展和系统复杂度的提生,要会出现越来越多的子项目,并且子项目之间功能存在重叠, 维护和部署也变更复杂,很难方便的扩展。

因此越来越多的公司采用分布式架构


分布式架构

将一个系统横向分成若干子系统或服务,实现服务性能的动态扩容。
这不但大幅提高服务处理能力, 且降低单一程序的开发维护以及部署难度。

从整体来看系统复杂度和部署难度是增加了, 但是可以通过 CI/CD 即持续集成和持续部署手段将繁杂的测试部署等环节尽可能自动化.


w:28em


使用分布式服务作为软件体系结构的最早记录可追溯到二十世纪 80 年代初。


SOA 架构

SOA 首先在 90 年代中期得名,当时一家名为 Gartner Group 的公司认识到了这个软件架构的新趋势,并在全球推广。通过这样做,他们设法大大加快了这种架构模式的采用和进一步发展。

SOA(全称:Service Oriented Architecture),中文意思为 “面向服务的架构”,你可以将它理解为一个架构模型或者一种设计方法,而并不是服务解决方案。其中包含多个服务, 服务之间通过相互依赖或者通过通信机制,来完成相互通信的,最终提供一系列的功能。一个服务通常以独立的形式存在与操作系统进程中。各个服务之间通过网络调用 。


SOA 通常使用 ESB(企业总线)来连接各个服务节点。各个服务之间,只需要和 ESB 进行通信,这个时候,各个应用之间的交互就会变得更加的清晰,业务架构/逻辑等,也会变得很清楚。原本杂乱没有规划的系统,梳理成了一个有规划可治理的系统。

bg right


微服务架构

“微服务”一词源于 Martin Fowler 的名为 Microservices 的博文.

简单地说,微服务是系统架构上的一种设计风格,可以认为 SOA 是微服务的超集。微服务架构重点强调的一个是"业务需要彻底的组件化和服务化",原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用称为微服务,微服务之间通过基于 HTTP 的 RESTful API 进行通信协作。每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写。


微服务和 SOA 的区别

  1. 微服务去中心化,去掉 ESB 企业总线。微服务不再强调传统 SOA 架构里面比较重的 ESB 企业服务总线,同时 SOA 的思想进入到单个业务系统内部实现真正的组件化。

  2. Docker 容器技术的出现,为微服务提供了更便利的条件,比如更小的部署单元,每个服务可以通过类似 Node 或者 SpringBoot 等技术跑在自己的进程中。

  3. SOA 注重的是系统集成方面,而微服务关注的是完全分离。


由于微服务是独立部署的,我们可以更准确地为每个服务评估性能容量,通过配合服务间的协作流程也可以更容易地发现系统的瓶颈位置,以及给出较为准确的系统级性能容量评估。


  • 使用微服务架构的厂商
    • 淘宝
    • 京东
    • 抖音
    • ... ...

bg fit right


bg fit


RPC(remote procedure call)

整个应用分散成多个服务使得整个系统变得更为复杂。我们需要在分布式开发中引入额外的技术,以解决服务之间交互和分布式部署导致的问题。

RPC(远程过程调用),即在本地调用远程机器的函数或者对象方法,使实际的体验和调用本地函数或者对象方法无异。

RPC 也是一种技术思想,HTTP 和 WebService 就是 RPC 思想的一种很好的体现方式,但 HTTP 已经满足不了企业内外部日益复杂的信息交互。因此许多优秀的 RPC 框架应运而生,比如著名的 Dubbo ,封装了一些像负载均衡、熔断降级、服务注册发现等面向对象的高级特性。


如何实施微服务

在实施微服务之前,我们必须要知道,微服务虽然有非常多吸引人的优点,但是也因为服务的拆分引发了诸多原本在单体应用中没有的问题。


  • 运维的新挑战:在微服务架构中,运维人员需要维护的进程数量会大大增加。我们需要运维人员有更多的技能来应对这样的挑战,运维过程需要更多的自动化,这就要求运维人员具备一定的开发能力来编排运维过程并让它们能自动运行起来。

  • 接口的一致性:虽然我们拆分了服务,但是业务逻辑上的依赖并不会消除,只是从单体应用中的代码依赖变为了服务间的通信依赖。我们需要更完善的接口和版本管理,或是严格地遵循开闭原则。


  • 分布式的复杂性:由于拆分后的各个微服务都是独立部署并运行在各自的进程内,它们只能通过通信来进行协作,所以分布式环境的问题都将是微服务架构系统设计时需要考虑的重要因素,比如网络延迟、分布式事务、异步消息等。

  • 尽管微服务架构有很多缺点和问题,但是其实现的敏捷开发和自动化部署等优点依然被广大优秀架构师和开发者所青睐.微服务架构并没有一个标准或正式的定义,


Martin Fowler 在 Microservices—文中,提炼出了微服务架构的九大特性,用于指导大家设计架构。

bg fit right


1. 服务组件化

在微服务架构中,需要我们对服务进行组件化分解。服务,是一种进程外的组件,它通过 HTTP 等通信协议进行协作,而不是像传统组件那样以嵌入的方式协同工作。每一个服务都独立开发、部署,可以有效避免一个服务的修改引起整个系统的重新部署。


bg fit


2. 按业务组织团队

在实施微服务架构时,需要采用不同的团队分割方法。由于每一个微服务都是针对特定业务的宽栈或是全栈实现,既要负责数据的持久化存储,又要负责用户的接口定义等各种跨专业领域的职能。因此,面对大型项目的时候,对于微服务团队的拆分更加建议按业务线的方式进行拆分,一方面可以有效减少服务内部修改所产生的内耗;另一方面,团队边界可以变得更为清晰.


w:20em

康威法则: 一个组织的结构应该反映其沟通结构。


bg fit


3. 做“产品”的态度

在实施微服务架构的团队中,每个小团队都应该以做产品的方式,对其产品的整个生命周期负责。持续关注服务的运作情况,并不断分析以帮助用户来改善业务功能。


4. 智能端点与哑管道

在微服务架构中,组件间的通信模式发生了改变,若仅仅将原本在进程内的方法调用改成 RPC 方式的调用,会导致微服务之间产生烦琐的通信,使得系统表现更为糟糕,所以,我们需要更粗粒度的通信协议。

Martin Flower 将微服务架构的服务通信理念称为“Smart endpoints and dumb pipes”, 简单翻译为“聪明的终端,愚蠢的管道”,大意是指强服务个体和弱通信。


在微服务架构中,通常会使用以下两种服务调用方式:

  • 使用 HTTP 的 RESTful API 或轻量级的消息发送协议
  • 通过在轻量级消息总线上传递消息,类似 RabbitMQ 等一些提供可靠异步交换的中间件。

在极度强调性能的情况下,有些团队会使用二进制的消息发送协议,例如 protobuf 即使是这样,这些系统仍然会呈现出“智能端点和哑管道”的特点,这是为了在易读性与高效性之间取得平衡。当然大多数 Web 应用或企业系统并不需要在这两者间做出选择,能够获得易读性已经是一个极大的胜利了。


5. 去中心化治理

在实施微服务架构时,通过采用轻量级的契约定义接口,使得我们对于服务本身的具体技术平台不再那么敏感,这样整个微服务架构系统中的各个组件就能针对其不同的业务特点选择不同的技术平台.


6. 去中心化管理数据

我们在实施微服务架构时,都希望让每一个服务来管理其自有的数据库,这就是数据管理的去中心化。

bg fit left

由于数据存储于不同的数据库实例中后,数据一致性也成为微服务架构中亟待解决的问题之一。(分布式锁,分布式事务)


7. 基础设施自动化

近年来云计算服务与容器化技术的不断成熟,在微服务架构中,务必从一开始就构建起“持续交付”平台来支撑整个实施过程:

  • 自动化测试:每次部署前的强心剂,尽可能地获得对正在运行的软件的信心。
  • 自动化部署:解放烦琐枯燥的重复操作以及对多环境的配置管理。

w:28em

Reference Link


8. 容错设计

微服务架构中,由于服务都运行在独立的进程中,所以存在部分服务出现故障,而其他服务正常运行的情况。由于服务之间存在调用链,因此在微服务架构中,快速检测出故障源并尽可能地自动恢复服务是必须被设计和考虑的。

通常,我们都希望在每个服务中实现监控和日志记录的组件,比如服务状态、断路器状态、吞吐量、网络延迟等关键数据的仪表盘等。


9. 演进式设计

在很多情况下,架构师都会以演进的方式进行系统的构建。

  • 在初期,以单体系统的方式来设计和实施,一方面系统体量初期并不会很大,构建和维护成本都不高,此时还不需要微服务架构。

  • 随着系统的发展或者业务的需要,架构师会将一些经常变动或是有一定时间效应的内容进行微服务处理,并逐渐将原来在单体系统中多变的模块逐步拆分出来,而稳定不太变化的模块就形成一个核心微服务存在于整个架构之中。


Spring Cloud

SpringCloud 的出现,可以说是对微服务架构的巨大支持和强有力的技术后盾,SpringCloud 是一个解决微服务架构实施的综合性解决框架的有序集合,利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。

w:20em


SpringCloud 核心部件:

w:28em


近几年人们对于微服务架构的热情非常高,无数的架构师和开发者在实际项目中实践该设计理念并为此付出了诸多努力,同时也分享了他们在微服务架构中针对不同应用场景出现的各种问题的各种解决方案和开源框架,其中也不乏国内互联网企业的杰出贡献。

  • 服务治理:阿里巴巴开源的 Spring Cloud Alibaba Nacos、Netflix 的 Eureka、Apache 的 Consul 等。
  • 分布式配置管理:百度的 Disconf、SpringCloud 的 Config、阿里巴巴开源的 Spring Cloud Alibaba Nacos 等。
  • 批量任务:当当网的 Elastic-Job、Linkedln 的 Azkaban、SpringCloud 的 Task 等。
  • 服务跟踪:京东的 Hydra、SpringCloud 的 Sleuth、Twitter 的 Zipkin 等。

早期 Netflix 和现在的 Alibaba 都是非常重要的 SpringCloud 的贡献者,他们都是微服务架构的先行者,他们的开源框架和解决方案都是我们在实施微服务架构时的参考。

w:28em


Spring Cloud Netflix

h:6em

Spring Cloud Alibaba

h:6em


上面列举了一些在实施微服务架构初期,就需要被我们考虑进去的问题,以及针对这些问题的开源解决方案。可以看到国内、国外的技术公司都在贡献着他们的智慧。我们搜索微服务架构的实施方案时会发现,几乎大部分的分享主要以理论或是一个粗轮廓框架为主,整合了来自不同公司或组织的诸多开源框架,并加入针对自身业务的一些优化,所以找不到一个完全相同的架构方案。


Spring Cloud 包含了多个子项目(针对分布式系统中涉及的多个不同开源产品,还可能会新增),挑选部分项目,如下所述。

  • Spring Cloud Config:配置管理工具,支持使用 Git 存储配置内容,可以使用它实现应用配置的外部化存储,并支持客户端配置信息刷新、加密/解密配置内容等。

  • Spring Cloud Netflix:核心组件,对多个 Netflix OSS 开源套件进行整合。

    • Eureka:服务治理组件,包含服务注册中心、服务注册与发现机制的实现。
    • Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。
    • Ribbon:客户端负载均衡的服务调用组件。
    • Feign:基于 Ribbon 和 Hystrix 的声明式服务调用组件。
    • Zuul:网关组件,提供智能路由、访问过滤等功能

  • Spring Cloud Alibaba: 核心组件,对多个 Alibaba 开源套件进行整合.

    • 流量控制和服务降级: Alibaba Sentinel 的流量控制、断路和系统自适应保护
    • 服务注册与发现:实例注册到阿里巴巴的 Nacos,通过负载均衡器 Ribbon,客户端可以通过 Spring 管理的 bean 发现实例。
    • 分布式配置:使用阿里巴巴 Nacos 作为数据存储
    • 事件驱动:构建与 Spring Cloud Stream RocketMQ Binder 连接的高度可扩展的事件驱动微服务
  • Spring Cloud Bus:事件、消息总线,用于传播集群中的状态变化或事件,以触发后续的处理,比如用来动态刷新配置等

  • Spring Cloud Cluster:针对 ZooKeeper、Redis、Hazelcast、Consul 的选举算法和通用状态模式的实现。

  • Spring Cloud Stream:通过 Redis、RabbitMQ 或者 Kafka 实现的消费微服务,可以通过简单的声明式模型来发送和接收消息。


  • Spring Cloud Security:安全工具包,提供在 Zuul 代理中对 OAuth2 客户端请求的中继器。
  • Spring Cloud Sleuth: SpringCloud 应用的分布式跟踪实现,可以完美整合 Zipkin
  • Spring Cloud ZooKeeper:基于 ZooKeeper 的服务发现与配置管理组件。
  • Spring Cloud Starters:SpringCloud 的基础组件,它是基于 SpringBoot 风格项目的基础依赖模块。

image-20220711131013102


Spring Cloud 版本说明

当我们通过搜索引擎查找一些 SpringCloud 的文章或示例时,往往可以在依赖中看到很多不同的版本名字,比如 Angel.SR6、Brixton.SR5 等,为什么 SpringCloud 没有像其他 Spring 的项目使用类似 l.x.x 的版本命名规则呢?这些版本之间又有什么区别呢?在学习之初,非常有必要弄清楚这些版本的意义和内容,这样才能在我们使用 SpringCloud 时,指导我们选择更为合适的版本进行架构与开发。


版本名与版本号

由于 SpringCloud 不像 Spring 社区其他一些项目那样相对独立,它是一个拥有诸多子项目的大型综合项目,可以说是对微服务架构解决方案的综合套件组合,其包含的各个子项目也都独立进行着内容更新与迭代,各自都维护着自己的发布版本号。因此每一个 Spring Cloud 的版本都会包含多个不同版本的子项目,为了管理每个版本的子项目清单,避免 SpringCloud 的版本号与其子项目的版本号相混
淆,没有采用版本号的方式,而是通过命名的方式。


这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序,比如最早的 Release 版本为 Angel,第二个 Release 版本为 Brixton 经过上面的解释,不难猜出,之前所提到的 Angel.SR6、Brixton.SR5 中的 SR6、SR5 就是版本号了。

当一个版本的 SpringCloud 项目的发布内容积累到临界点或者一个严重 bug 解决可用后,就会发布一个“servicereleases”版本,简称 SRX 版本,其中 X 是一个递增的数字,所以 Brixton.SR5 就是 Brixton 的第 5 个 Release 版本。


SpringCloud 与 SpringBoot 的版本兼容关系如下:

Release Train Boot Version
2021.0.x aka Jubilee 2.6.x
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)
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

注意:在本教程,主要以 Hoxton.SR10 版本进行案例演示.因此对应的 SpringBoot 版本是 2.2.x ~ 2.3.x 版本.


活动手册

我们在构建 SpringBoot 应用的时候,总是需要一整套的符合 MVC 规范的简单模板文件,为了避免重复性操作,我们可以使用一些工具类帮我们完成这部分的工作.比如说 Intellij IDEA 的一款优秀插件: EasyCode。

EasyCode 是基于 IntelliJ IDEA 开发的代码生成插件,支持自定义任意模板(Java,html,js,xml)。只要是与数据库相关的代码都可以通过自定义模板来生成。支持数据库类型与 java 类型映射关系配置。支持同时生成生成多张表的代码。每张表有独立的配置信息。完全的个性化定义,规则由你设置。


活动 1.1 使用 EasyCode 插件生成辅助代码

创建项目- 推荐使用 Spring Initializr 创建项目

  • 使用https://start.aliyun.com/ 作为启动页

  • 建议使用 JDK11 或 JDK17


  • 依赖:

    • spring-boot-starter-parent 2.3.12.RELEASE

    • springboot-starter-web - 相关

    • springboot-starter-jdbc

    • spring-data-commons 2.3.9.RELEASE

    • mybatis-spring-boot-starter 2.1.4

    • mysql-connector-java (5.1.x)

    • spring-boot-starter-actuator

    • lombok optional

    • spring-boot-configuration-processor optional

    • spring-boot-devtool optional


步骤 1. 安装 EasyCode 插件

h:16em


步骤 2. 配置 EasyCode 插件

h:16em


步骤 3. 配置数据库连接

按照活动手册要求创建数据库, 然后Views -> Tool Windows -> Database

h:16em


步骤 4. 配置模板

settings -> Other Settings -> EasyCode -> templates

如果使用 lombok,需要在 entity 类模板中添加 @Data 注解

##使用全局变量实现默认包导入
$!{autoImport.vm}
import java.io.Serializable;
import lombok.Data;
##使用宏定义实现类注释信息
#tableComment("实体类")
@Data
public class $!{tableInfo.name} implements Serializable {
    private static final long serialVersionUID = $!tool.serial();
#foreach($column in $tableInfo.fullColumn)
    #if(${column.comment})/**
     * ${column.comment}
     */#end
    private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end

dao 接口类模板示例

queryAllByLimit代码需要修改才能使用

public interface $!{tableName} {

    /**
     * 通过ID查询单条数据
     * @param $!pk.name 主键
     * @return 实例对象
     */
    $!{tableInfo.name} queryById($!pk.shortType $!pk.name);

    /**
     * 查询指定行数据
     * @param $!tool.firstLowerCase($!{tableInfo.name}) 查询条件
     * @param pageable         分页对象
     * @return 对象列表
     */
    List<$!{tableInfo.name}> queryAllByLimit(
      @Param("entity") $!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name}),
      @Param("pageable") Pageable pageable
    );

mapper.xml 模板示例

mapper.xml文件保存路径需要调整与 SpringBoot 默认的一致

##引入mybatis支持
$!{mybatisSupport.vm}

##设置保存名称与保存位置
$!callback.setFileName($tool.append($!{tableInfo.name}, "Dao.xml"))
$!callback.setSavePath($tool.append($modulePath, "/src/main/resources/mappers"))

mapper.xml 模板示例

queryAllByLimit的查询需要修改才能使用

    <!--查询单个-->
    <select id="queryById" resultMap="$!{tableInfo.name}Map">
        select
          #allSqlColumn()

        from $!tableInfo.obj.name
        where $!pk.obj.name = #{$!pk.name}
    </select>

    <!--查询指定行数据-->
    <select id="queryAllByLimit" resultMap="$!{tableInfo.name}Map">
        select
          #allSqlColumn()
        from $!tableInfo.obj.name
        <where>
#foreach($column in $tableInfo.fullColumn)
            <if test="entity.$!column.name != null
  #if($column.type.equals("java.lang.String"))
                  and entity.$!column.name != ''
    #end      ">
                and $!column.obj.name = #{entity.$!column.name}
            </if>
#end
        </where>
        <if test="pageable.sort != null and pageable.sort.orders.size > 0">
            order by
            <foreach collection="pageable.sort.orders" item="order" separator=",">
                ${order.property} ${order.direction}
            </foreach>
        </if>
        limit #{pageable.offset}, #{pageable.pageSize}
    </select>

步骤 5. 生成代码

application.properties 需要修改mapper.xml的生成路径

# 应用名称
spring.application.name=EasyCodeDemo
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 数据源名称
spring.datasource.name=MySQLDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/course?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=niit1234
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件 - EasyCode自动生成的包为mapper, 需修改
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.example.easycodedemo.mybatis.entity
# debug
logging.level.com.example.easycodedemo=debug
spring.jpa.show-sql=true

可以选择多表, 选择生成代码


指定生成规则, 点击 OK 进行生成


指定 Mapper 的包扫描路径

package com.example.easycodedemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.easycodedemo.dao")
public class EasyCodeDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(EasyCodeDemoApplication.class, args);
    }

}

测试:

  • 对于 get 请求可以直接使用浏览器窗口进行测试(只支持 get)
    • 或者编写页面使用 ajax 进行测试
  • 或者 idea 的插件 (方便)
    • HTTP Client(Builtin)
    • RestfulTool(MarketPlace 免费)
    • Restful Fast Request(收费)
  • 另外还可以使用专用软件进行测试 (功能强大)
    • curl
    • postman
    • apifox
    • apipost

这里使用 RestfulTool 进行测试: body 设置{}表示无条件分页查询

bg right


分页查询报错的问题解决

测试http://localhost:8080/course/接口收到报错如下:

[Request processing failed;
nested exception is java.lang.IllegalStateException:
No primary or default constructor found for class org.springframework.data.domain.PageRequest] with root cause

java.lang.NoSuchMethodException: org.springframework.data.domain.PageRequest.<init>()

新建config包, 并创建一个自定义参数解析器PageRequestHandlerMethodArgumentResolver, 让它也能解析Pageable的子类 PageRequest

@Component
public class PageRequestHandlerMethodArgumentResolver extends PageableHandlerMethodArgumentResolver {

    /**
     * {@link PageableHandlerMethodArgumentResolver} SpringData 提供的只能解析 {@link Pageable} 类型的
     * 这里让它也能解析它的子类 {@link PageRequest}
     *
     * @param parameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Pageable.class.isAssignableFrom(parameter.getParameterType());
    }
}

接着创建WebConfig 配置类

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private PageRequestHandlerMethodArgumentResolver pageRequestHandlerMethodArgumentResolver;

    /**
     * 添加自定义参数解析器
     *
     * @param argumentResolvers
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(pageRequestHandlerMethodArgumentResolver);
    }
}

分页和排序支持:

http://localhost:8080/course?size=2&page=0&sort=cid,desc&sort=cname,asc

表示:

  1. 页容量为2, 显示第1页的数据
  2. 二次排序: 先按cid倒序, 再按cname正序

小问题:

Spring Cloud 为以下哪些操作提供了一种简单的开发方式?

  • A. 服务治理
  • B. 智能路由
  • C. 微代理
  • D. 控制总线


谢谢

Views: 44