注意: 此阶段学习推荐的电脑配置,至少配备4核心CPU(主频3.0Ghz以上)+16GB内存,否则卡到你怀疑人生。
前面我们讲解了 SpringBoot 框架,通过使用 SpringBoot 框架,我们的项目开发速度可以说是得到了质的提升。同时,我们对于项目的维护和理解,也会更加的轻松。可见,SpringBoot 为我们的开发带来了巨大便捷。而这一部分,我们将基于 SpringBoot,继续深入到企业实际场景,探讨微服务架构下的 SpringCloud。这个部分我们会更加注重于架构设计上的讲解,弱化实现原理方面的研究。
要说近几年最火热的话题,那还得是微服务,那么 什么是微服务 呢?
我们可以先从技术的演变开始看起: 在我们学习 JavaWeb 之后,一般的网站开发模式为 Servlet + JSP。后来我们在学习了 SSM 之后,进行了前后端分离。通过使用 SpringBoot,我们几乎可以很快速地开发一个高性能的单体应用,只需要启动一个服务端,我们整个项目就开始运行了,各项功能融于一体,开发起来也更加轻松。
但是随着我们项目的不断扩大,单体应用似乎显得有点乏力了。
随着越来越多的功能不断地加入到一个 SpringBoot 项目中,随着接口不断增加,整个系统就要在同一时间内响应更多类型的请求,显然,这种扩展方式是不可能无限使用下去的,总有一天,这个 SpringBoot 项目会庞大到运行缓慢。并且所有的功能如果都集成在单端上,那么所有的请求都会全部汇集到一台服务器上,对此服务器造成巨大压力。
传统单体架构应用随着项目规模的扩大,实际上会暴露越来越多的问题,尤其是一台服务器无法承受庞大的单体应用部署,并且单体应用的维护也会越来越困难,我们得寻找一种新的开发架构来解决这些问题了。
Martin Fowler 在2014年提出了“微服务”
架构,它是一种全新的架构风格。
可见,采用微服务架构,更加能够应对当今时代下的种种考验,传统项目的开发模式,需要进行架构上的升级。
回到目录…
前面我们介绍了微服务架构的优点,那么同样也存在着诸多的问题:
服务注册和发现
、容灾处理
、负载均衡
、配置管理
等。所以,为了更好地解决这些问题,SpringCloud 正式登场。
SpringCloud 是 Spring 提供的一套分布式解决方案,集合了一些大型互联网公司的开源产品,包括诸多组件,共同组成 SpringCloud 框架。并且,它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册
、配置中心
、消息总线
、负载均衡
、熔断机制
、数据监控
等,都可以用 Spring Boot 的开发风格做到一键启动和部署。
由于中小型公司没有独立开发自己的分布式基础设施的能力,使用 SpringCloud 解决方案能够以最低的成本应对当前时代的业务发展。
可以看到,SpringCloud 整体架构的亮点是非常明显的,分布式架构下的各个场景,都有对应的组件来处理,比如基于 Netflix
(奈飞)的开源分布式解决方案提供的组件:
Eureka
- 实现服务治理(服务注册与发现),我们可以对所有的微服务进行集中管理,包括他们的运行状态、信息等。Ribbon
- 提供客户端的软件负载均衡算法(现在被SpringCloudLoadBalancer
取代)。Hystrix
- 断路器,保护系统,控制故障范围。暂时可以跟家里电闸的保险丝类比,当触电危险发生时能够防止进一步的发展。Zuul
- 具有api网关,路由,负载均衡等多种作用。就像我们的路由器,可能有很多个设备都连接了路由器,但是数据包要转发给谁则是由路由器在进行(已经被SpringCloudGateway
取代)。Config
- 配置管理,可以实现配置文件集中管理。当然,这里只是进行简单的了解即可,实际上微服务的玩法非常多,我们后面的学习中将会逐步进行探索。
回到目录…
现在我们重新设计一下图书管理系统项目,将原有的大型进行拆分 。一定要尽可能保证单一职责,相同的业务不要在多个微服务中重复出现。如果出现需要借助其他业务完成的服务,那么可以使用服务之间相互调用的形式来实现:
②然后不需要勾选任何依赖,直接创建即可,项目创建完成并初始化后,我们删除父工程的无用文件,只保留必要文件,像下面这样:
①接着我们就可以按照我们划分的服务,创建子工程了,创建一个新的 Maven 项目,注意父项目要指定为我们一开始创建的项目,子项目命名随意:
②子项目创建好之后,接着我们在子项目中创建 SpringBoot 的启动主类:
③接着我们点击运行,即可启动子项目了,实际上这个子项目就一个最简单的 SpringBoot web 项目,注意启动之后最下方有弹窗,我们点击 “Use service”,这样我们就可以在控制台的services
项实时查看大项目中的微服务了:
①接着我们以同样的方法,创建其他的子项目,注意我们最好将其他子项目的端口设置的不一样,不然会导致端口占用,我们分别为它们创建application.yml
文件:
②接着我们来尝试启动一下这三个服务,正常情况下都是可以直接启动的:
可以看到它们分别运行在不同的端口上,这样,就方便不同的程序员编写不同的服务了,提交当前项目代码时的冲突率也会降低。
③后续用需要连接数据库,添加数据源信息:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/library?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
回到目录…
父项目 pom.xml:
首先在父项目中添加 MySQL 驱动和 Lombok 依赖,因为这两个几乎所有项目都会用到。
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
由于不是所有的子项目都需要用到 Mybatis 和 SpringCloud 组件,我们在父项目中只进行版本管理即可:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>2021.0.1version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
dependencies>
dependencyManagement>
子项目 pom.xml:
我们在子项目中添加 web 和 mybatis 的依赖:(每个子项目各自添加自己需要的依赖)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
回到目录…
分布式系统要符合单一职责,每个子模块只实现自己的业务功能,只能访问自己的数据库和库表,所以我们可以分成多个库。我这里为了方便就只建了一个库。
可以直接执行 sql 语句:
CREATE SCHEMA `library` DEFAULT CHARACTER SET utf8mb4 ;
CREATE TABLE `library`.`user` (
`uid` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
`age` INT NOT NULL,
`sex` ENUM('男', '女') NOT NULL,
PRIMARY KEY (`uid`));
CREATE TABLE `library`.`book` (
`bid` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(45) NOT NULL,
`desc` VARCHAR(45) NOT NULL,
PRIMARY KEY (`bid`));
CREATE TABLE `library`.`borrow` (
`id` INT NOT NULL AUTO_INCREMENT,
`uid` INT NOT NULL,
`bid` INT NOT NULL,
PRIMARY KEY (`id`),
INDEX `f_uid_idx` (`uid` ASC) COMMENT '外键连接user表中的uid',
INDEX `f_bid_idx` (`bid` ASC) COMMENT '外键连接book表中的bid',
UNIQUE INDEX `unique_bid_uid` (`uid` ASC, `bid` ASC) COMMENT '唯一索引:uid和bid的组合不重复',
CONSTRAINT `f_uid`
FOREIGN KEY (`uid`)
REFERENCES `library`.`user` (`uid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `f_bid`
FOREIGN KEY (`bid`)
REFERENCES `library`.`book` (`bid`)
ON DELETE NO ACTION
ON UPDATE NO ACTION);
回到目录…
这是我们新建的模块,用于存放所有业务模块中需要用到的公共实体类(对应数据库)。
我们来写用户查询相关的业务:
● 持久层: mapper 接口
@Repository
@Mapper
public interface UserMapper {
@Select("select * from user where uid = #{uid}")
User getUserById(int uid);
}
● service 业务层: 具体业务逻辑
public interface UserService {
User getUserById(int uid);
}
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public User getUserById(int uid) {
return userMapper.getUserById(uid);
}
}
● Controller 控制层: 请求和响应
@RestController
public class UserController {
@Resource
private UserService userService;
@GetMapping("/user/{uid}")
public User findUserById(@PathVariable("uid") int uid) {
return userService.getUserById(uid);
}
}
其它子工程也是同样的逻辑编写业务。这样,我们一个完整项目的就拆分成了多个微服务,不同微服务之间是独立进行开发和部署的。
回到目录…
我们来写书籍查询相关的业务:
● 持久层: mapper 接口
@Mapper
@Repository
public interface BookMapper {
@Select("select * from book where bid = #{bid}")
Book getBookById(int bid);
}
● service 业务层: 具体业务逻辑
public interface BookService {
Book getBookById(int bid);
}
@Service
public class BookServiceImpl implements BookService {
@Resource
private BookMapper bookMapper;
@Override
public Book getBookById(int bid) {
return bookMapper.getBookById(bid);
}
}
● Controller 控制层: 请求和响应
@RestController
public class BookController {
@Resource
private BookService bookService;
@GetMapping("/book/{bid}")
public Book findBookById(@PathVariable("bid") int bid) {
return bookService.getBookById(bid);
}
}
回到目录…
前面我们完成了用户信息查询 和 图书信息查询,现在我们来接着完成借阅服务 (此服务中需要用户信息和图书信息)。为了不违反单一职责原则,我们可以让一个服务去调用另一个服务来获取信息。
这样,图书管理微服务和用户管理微服务相对于借阅记录,就形成了一个生产者和消费者的关系,前者是生产者,后者便是消费者。
现在我们先将借阅关联信息查询完善了:
● 视图实体类: 返回给客户端看的实体类 (用户的借阅详细信息,而不仅仅是id关系)
@Data
@AllArgsConstructor // 生成全参数构造方法
public class UserBorrowView {
private User user;
private List<Book> bookList;
}
● 持久层: mapper 接口
@Mapper
@Repository
public interface BorrowMapper {
@Select("select * from borrow where uid = #{uid}")
List<Borrow> getBorrowsByUid(int uid);
@Select("select * from borrow where bid = #{bid}")
List<Borrow> getBorrowsByBid(int bid);
@Select("select * from borrow where uid = #{uid} and bid = #{bid}")
Borrow getBorrow(int uid, int bid);
}
● service 业务层: 具体业务逻辑
public interface BorrowService {
UserBorrowView getBorrowViewByUid(int uid);
}
@Service
public class BorrowServiceImpl implements BorrowService {
@Resource
private BorrowMapper borrowMapper;
@Override
public UserBorrowView getBorrowViewByUid(int uid) {
// 现在拿到借阅关联信息了,怎么调用其他服务获取信息呢?
List<Borrow> borrowList = borrowMapper.getBorrowsByUid(uid);
// RestTemplate 支持多种方式的远程调用
RestTemplate template = new RestTemplate();
// 这里通过调用 getForObject 来请求其他服务,并将结果自动进行封装
// 获取 User 信息
User user = template.getForObject("http://127.0.0.1:8083/user/"+uid, User.class);
List<Book> bookList = new ArrayList<>();
for(Borrow borrow: borrowList) {
// 获取每一本 Book 信息
Book book = template.getForObject("http://127.0.0.1:8081/book/"+borrow.getBid(), Book.class);
bookList.add(book);
}
return new UserBorrowView(user, bookList);
}
}
● Controller 控制层: 请求和响应
@RestController
public class BorrowController {
@Resource
private BorrowService borrowService;
@GetMapping("/borrow/{uid}")
public UserBorrowView findUserBorrows(@PathVariable("uid") int uid) {
return borrowService.getBorrowViewByUid(uid);
}
}
现在我们访问即可拿到数据:
可以看到结果正常,没有问题,远程调用成功。
这样,一个简易的图书管理系统的分布式项目就搭建完成了,这里记得把整个项目压缩打包备份一下,之后我们学习 SpringCloud Alibaba 也需要进行配置。
回到目录…
总结:
提示:这里对文章进行总结:
本文是对SpringCloud的学习,认识什么是微服务、它的优缺点,并且认识了微服务架构SpringCloud,介绍了它的五大组件,还有微服务项目搭建的过程。之后的学习内容将持续更新!!!