实验3 微服务介绍以及开发环境搭建
目的与要求
掌握IDEA、JDK、Maven、MySQL的下载、安装、配置与使用、搭建基础框架并实现服务间调用。
实验内容
2.1 系统架构的演进、微服务介绍。
2.2 安装配置JDK。
2.3 安装配置Maven。
2.4 熟悉IDEA安装、配置与开发项目的方法。
2.5 安装配置MySQL和Navicat,创建练习用的数据库和表。
2.6 开发运行基于Spring Boot的建筑工程管理系统基础模块以及模块间的简单调用。
实验指导
3.1 微服务在企业开发中的地位
也许很多企业并没有真正去使用微服务项目架构进行开发,但是在面试的过程中仍然有极大的可能性会问到关于微服务的相关知识。这也就是行业内经常调侃的说法:"面试造火箭,工作打螺丝",是否熟悉微服务开发架构,往往是判别初级开发人员和高级开发人员的主要条件。
3.2 从单体应用架构演变为微服务架构
随着互联网的发展,网站应用的规模也在不断的扩大,进而导致系统架构也在不断的进行变化。从互联网早期到现在,系统架构大体经历了下面几个过程: **单体应用架构---****>垂直应用架构--- >分布式架构--- >SOA架构--- >**微服务架构。 在讲解微服务技术之前,我们先来了解一下每种系统架构是什么样子的, 以及他们各有什么优缺点。
3.2.1 单体应用架构
以传统电子商务系统为例,可能会涉及到用户模块、商品模块、订单模块、购物车模块、权限模块、内容模块等等,这些模块一般都是在一个项目中开发,部署的时候也是打包成一个War包或者Jar包,然后部署到一台Tomcat服务器上,非常的简单便捷,包括大家前段时间所学的SpringBoot+Vue项目,在部署和运行时,我们也是打包成一个包运行。
优势:
- 项目架构简单,小型项目的话,开发成本低,项目部署在一个节点上,维护方便。
劣势:
- 全部功能集成在一个工程中,对于大型项目来讲不易开发和维护。
- 项目模块之间紧密耦合,单点容错率低,无法针对不同模块进行针对性优化和水平扩展。
3.2.2 垂直应用架构
随着系统的访问量的逐渐增大,单体应用要么更换更加强大的服务器来部署节点(我们称为纵向扩展),要么增加多个节点来部署项目(我们称为横向扩展)。但是这时候会发现,其实并不是所有的模块都会有比较大的访问量。还是以上面的电商为例子,用户访问量的增加可能影响的只是用户和订单模块,但是对消息模块的影响就比较小。那么此时我们希望只多增加几个订单模块,而不增加消息模块。此时单体应用就做不到了,垂直应用就应运而生了。
所谓的垂直应用架构,就是将原来的一个应用拆成互不相干的几个应用,以提升效率。比如我们可以将上面电商的单体应用拆分成:
- 电商系统(用户管理、商品管理、订单管理)
- 后台系统(用户管理、订单管理、客户管理)
- CMS系统(广告管理、营销管理)
这样拆分完毕之后,一旦用户访问量变大,只需要增加电商系统的节点就可以了,而无需增加后台和CMS的节点。
优势:
- 系统拆分实现了流量分担,解决了并发问题,而且可以针对不同模块进行优化和水扩展。
- 一个系统的问题不会影响到其他系统 ,提高容错率。
劣势:
- 各模块间独立性非常强,所以会有很多重复性工作,比如我们在多个模块中都会用到查询用户数据的功能,那我们必须在每个模块都实现一次,非常影响开发效率。
- 随着模块越来越多,重复的功能也越来越多,我们必须要把一些核心功能抽离出来,作为独立服务,为其他模块提供服务,来提高代码的复用率。
3.2.3 分布式应用架构
当垂直应用越来越多,重复的业务代码就会越来越多。这时候,我们就思考可不可以将重复的代码抽取出来,做成统一的业务层作为独立的服务,然后由前端控制层调用不同的业务层服务,这就产生了新的分布式系统架构。它将把工程拆分成表现层和服务层两个部分,服务层中包含业务逻辑。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。
优势:
- 抽取公共的功能为服务层,提高代码复用性。
劣势:
- 系统间耦合度变高,调用关系错综复杂,难以维护。
- 系统间相互调用,模块少的时候并不明显,当模块数量变多,调用关系也变得及其复杂,难以维护。对服务进行治理迫在眉睫,SOA出现,资源调度和治理中心(SOA Service Oriented Architecture),使用SOA来解决服务间调用管理的自动调节。
3.2.4 SOA架构
在分布式架构下,当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心对集群进行实时管理。此时,用于资源调度和治理中心(SOA Service Oriented Architecture)是关键。
优势:
- 使用治理中心(ESB\dubbo)解决了服务间调用关系的自动调节。
劣势:
- 服务间会有依赖关系,一旦某个环节出错会影响较大(服务雪崩)。
- 服务关系复杂,运维、测试部署困难。
3.2.5 微服务架构
微服务架构从某种角度讲,就是SOA架构的继续发展的下一步,微服务强调的是**"彻底的拆分"**,如果一定要说微服务和SOA的区别,那可以从拆分粒度上来讲,微服务拆分的更细,做到单一职责。
- 微服务架构比 SOA架构粒度会更加精细,让专业的人去做专业的事情(专注),目的提高效率,每个服务于服务之间互不影响,微服务架构中,每个服务必须独立部署,微服务架构更加轻巧,轻量级。
- SOA 架构中可能数据库存储会发生共享,微服务强调独每个服务都是单独数据库,保证每个服务于服务之间互不影响。项目体现特征微服务架构比 SOA 架构更加适合与互联网公司敏捷开发、快速迭代版本,因为粒度非常精细。
一旦采用了微服务系统架构,那势必会遇到一些问题:
- 这么多的小服务,如何管理他们(服务治理 注册中心[服务的注册、发现、剔除]):Nacos。订单服务会调用库存服务、商品服务、积分服务,调用的URL地址肯定是配置在订单服务代码中的,如果有一天库存服务的服务器发生了变更,会变得非常麻烦。
- 这么多的小服务,他们之间如何进行通讯? Feign
- 这么多的小服务器,客户端如何访问他们? Gateway 微服务的大门,前端请求网关,网关负责转发。
- 一旦出现了问题,如何自处理?容错 Sentinel
- 一旦出现了问题,如何排查问题,定位问题?链路追踪 Skywalking
3.3 常见的微服务架构
- dubbo:zookeeper + dubbo + Spring Mvc/Spring Boot
- SpringCloud:全家桶 + NetFlix (闭源了,也就是不再更新了)
- Spring Cloud Alibaba 目前市面上主流的微服务框架
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发微服务架构的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发微服务架构。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统。
这里给大家这么一个公式,这个叫做:"3 加 2"。
3 指的就是图中深色的部分,其实它就是 Spring Cloud 标准,一共有 3 层。中间颜色最深的部分就是及整个微服务最核心的内容,包括了" RPC调用"以及"服务注册与发现"。第二层,也就是围绕着核心的这一圈,是一些辅助微服务更好的工作功能,包括了负载均衡、路由、网关、断路器,还有就是追踪等等这些内容。再外层的话,主要是一些分布式云环境里通用能力。
"3 加 2"中的"2",指的就是上图中最外面这一圈。这一部分就是这个我们 Spring Cloud Alibaba 的一个定义,它其实包含两个部分的内容:
- 右上部分是对于 Spring Cloud 标准的实现。例如,我们通过 Dubbo 实现了 RPC 调用功能,通过 Nacos 实现了"服务注册与发现"、"分布式配置",通过 Sentinel 实现了断路器等等,这里就不一一列举了。
- 左下部分是我们 Spring Cloud Alibaba 对阿里云各种服务的集成。可能很多同学会有这样的一个问题:为什么要加上这一部分呢?此时回头审视一下 Spring Cloud ,它仅仅是一个微服务的一个框架。但是在实际生产过程中,单独使用微服务框架其实并不足以支撑我们去构建一个完整的系统。
所以这部分是用阿里帮助开发者完成微服务以外的云产品集成的功能。
这里可能会很多同学会有这么一个担心:是不是使用了 Spring Cloud Alibaba,就会被阿里云平台绑定呢?在此,我们明确的告诉大家,这是不会的。为什么这么说呢?如上面说的,"3 加 2"中的 2 是被分为两个部分的。其中对 Spring Cloud 的实现是完全独立的,开发者可以只是用这部分实现运行在任何云平台中。当然,另一部分,由于天然是对阿里云服务的集成,这部分是和平台相关的。这里给开发者充分的自由,选择只是用其中的部分还是全部产品。当然,我们也非常欢迎开发者选择使用阿里云的全套服务,我们也会尽量保证使用整套产品时的连贯性与开发的便利性。
3.4 常用的微服务组件
3.5 Spring Cloud Alibaba环境搭建
- 安装配置JDK,版本1.8+。参考:1.8.0_131

- 安装Maven,版本3.2.x+。我安装的是3.9.9

- 安装IDEA。
- 安装MySQL和Navicat。
3.6 搭建Spring Cloud Alibaba框架
由于 Spring Boot 3.0,Spring Boot 2.7~2.4 和 2.4 以下版本之间变化较大,目前企业级客户老项目相关 Spring Boot 版本仍停留在 Spring Boot 2.4 以下,为了同时满足存量用户和新用户不同需求,社区以 Spring Boot 3.0 和 2.4 分别为分界线,同时维护 2022.x、2021.x、2.2.x 三个分支迭代。版本对应关系图,可以参看图3.6。
(1)创建一个建筑工程管理系统的Maven项目
- Name: 项目名,ETOAK-PMS(Etoak Project Manager System)。
- Location: 项目地址,创建一个空的文件夹,最好不要出现中文路径。
- Language: 编程语言,选择Java。
- Build System: 系统构建,选择Maven。
- JDK: JDK版本,选择8。
- GroupId: com.etoak.java
- ArtifactId: PMS

创建完成后,会生成所示的目录结构的工程。
(1)PMS作为父工程,修改PMS项目下的pom.xml,添加Maven依赖,如下代码所示。
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/>
</parent>
<groupId>com.eotak.java</groupId>
<artifactId>PMS</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 父工程用 pom -->
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<spring.cloud.version>2021.0.1</spring.cloud.version>
<spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
如果Maven下载慢、或者无法下载部分依赖,可以修改IDEA的默认Maven的配置,添加一个阿里的镜像。一般默认配置文件在C:\Users\gao.m2\settings.xml
xml
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>

(2)PMS作为父工程,在PMS下创建三个子模块,分别为engineer (工程)、worker (员工)、archive(文档)。创建子模块的过程如所示。
右键PMS -> New -> Module -> 填写信息。


同理,创建其他两个子模块,worker和archive。因为PMS作为父工程存在,src目录没有实际作用,可以删除。最终项目格式如所示。

修改子模块的pom.xml,添加如下依赖:
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

(3)为每个子模块创建一个启动类,下面以engineer子模块为例,子模块自动生成的Main类可以直接删除。
在engineer/src/main/java/com/etoak/java目录下,新建EngineerApplication类,并修改代码如下。
java
package com.etoak.java;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EngineerApplication {
public static void main(String[] args) {
SpringApplication.run(EngineerApplication.class, args);
}
}

其他两个子模块的启动类为:WorkerApplication和ArchiveApplication
java
package com.etoak.java;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WorkerApplication {
public static void main(String[] args) {
SpringApplication.run(WorkerApplication.class, args);
}
}
java
package com.etoak.java;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ArchiveApplication {
public static void main(String[] args) {
SpringApplication.run(ArchiveApplication.class, args);
}
}
(4)为每个子模块设置端口号。在resources目录下新建一个file,名称为application.yml,并修改文件内容。engineer模块设置端口号为8081,archive模块设置为8082,worker模块设置为8083。
server:
port: 808X
yaml
server:
port: 8083
yaml
# archive模块
server:
port: 8082
yaml
# engineer模块
server:
port: 8081
(5)刷新一下Maven,会发现Application启动类的配置已经自动设置好了。
(6)添加Controller控制层代码
在com.etoak.java包下添加controller包,并创建一个EngineerController类(以Engineer模块为例,其他两个模块可以参考此模块),代码如下:

java
package com.etoak.java.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/engineer")
public class EngineerController {
@RequestMapping("/add")
public String add(){
System.out.println("工程添加成功!");
return "工程添加成功!";
}
}
(7)启动EngineerApplication,观察控制台是否有抛出异常信息,如果没有,启动成功。之后打开浏览器,地址栏输入http://localhost:8081/engineer/add

另外两个模块同理
java
package com.etoak.java.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/archive")
public class ArchiveController {
@RequestMapping("/upload")
public String upload(){
System.out.println("工程添加成功!");
return "工程添加成功!";
}
}
java
package com.etoak.java.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/worker")
public class WorkerController {
@RequestMapping("/add")
public String add(){
System.out.println("工程添加成功!");
return "工程添加成功!";
}
}
3.7 Services服务面板
在多服务启动下,控制台查看比较繁琐,可以通过Services面板进行服务配置。打开面板后,Add Service -> Run Configuration Type -> Spring Boot 会自动把之前的配置加载过来。


3.8 使用RestTemplate尝试进行服务之间的调用
(1)在Engineer模块添加RestTemplate配置类,在com.etoak.java包下创建config包,并在config包下新建RestTemplateConfig类,代码如下:
java
package com.etoak.java.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

(2)修改EngineerController,在add方法中调用文档中心Archive模块的upload接口,使用RestTemplate的getForObject方法,具体方法的含义就不详细说了,这属于spring boot的相关内容。代码如下:
java
package com.etoak.java.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/engineer")
public class EngineerController {
@Autowired
RestTemplate restTemplate;
@RequestMapping("/add")
public String add(){
System.out.println("添加工程基础信息..");
// 调用 Archive 模块接口(上传文件)
String result = restTemplate.getForObject(
"http://localhost:8082/archive/upload",
String.class
);
System.out.println("工程文件上传结果:" + result);
System.out.println("工程添加成功!");
return "工程添加成功!";
}
}
(3)重启服务,
- 先启动 ArchiveApplication(8082)
- 再启动 EngineerApplication(8081)
使用浏览器调用http://localhost:8081/engineer/add

Engineer 控制台打印:

Archive 控制台打印:

(4)上述的服务间的通信会有一个隐藏的问题,不知道你看出来没有。如果有一天,你将Archive模块的部署地址进行了修改,不管是IP地址改了还是端口号改了,Engineer模块势必会收到影响,那我也要同时修改EngineerController代码。也就是说被调用者发生变动,调用者也需要同步做出修改,否则服务将不可用。
当服务数量达到一定规模,服务间的调用必然会非常复杂,不可能每次重新部署模块,都要去查找哪些地方受影响,这么做难免有所遗漏。
所以如何对这些服务进行管理,就是非常关键的问题。下一节我们学习如何使用注册中心来解决上面的问题。