注意: 此阶段学习推荐的电脑配置,至少配备4核心CPU(主频3.0Ghz以上)+16GB内存,否则卡到你怀疑人生。
前面我们讲解了SpringBoot框架,通过使用SpringBoot框架,我们的项目开发速度可以说是得到了质的提升。同时,我们对于项目的维护和理解,也会更加的轻松。可见,SpringBoot为我们的开发带来了巨大便捷。而这一部分,我们将基于SpringBoot,继续深入到企业实际场景,探讨微服务架构下的SpringCloud。这个部分我们会更加注重于架构设计上的讲解,弱化实现原理方面的研究。
要说近几年最火热的话题,那还得是微服务,那么什么是微服务呢?
我们可以先从技术的演变开始看起,在我们学习JavaWeb之后,一般的网站开发模式为Servlet+JSP,但是实际上我们在学习了SSM之后,会发现这种模式已经远远落后了,第一,一个公司不可能去招那么多同时会前端+后端的开发人员,就算招到,也并不一定能保证两个方面都比较擅长,相比前后端分开学习的开发人员,显然后者的学习成本更低,专注度更高。因此前后端分离成为了一种新的趋势。通过使用SpringBoot,我们几乎可以很快速地开发一个高性能的单体应用,只需要启动一个服务端,我们整个项目就开始运行了,各项功能融于一体,开发起来也更加轻松。
但是随着我们项目的不断扩大,单体应用似乎显得有点乏力了。
随着越来越多的功能不断地加入到一个SpringBoot项目中,随着接口不断增加,整个系统就要在同一时间内响应更多类型的请求,显然,这种扩展方式是不可能无限使用下去的,总有一天,这个SpringBoot项目会庞大到运行缓慢。并且所有的功能如果都集成在单端上,那么所有的请求都会全部汇集到一台服务器上,对此服务器造成巨大压力。
可以试想一下,如果我们的电脑已经升级到i9-12900K,但是依然在运行项目的时候缓慢,无法同一时间响应成千上万的请求,那么这个问题就已经不是单纯升级机器配置可以解决的了。
传统单体架构应用随着项目规模的扩大,实际上会暴露越来越多的问题,尤其是一台服务器无法承受庞大的单体应用部署,并且单体应用的维护也会越来越困难,我们得寻找一种新的开发架构来解决这些问题了。
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.
Martin Fowler在2014年提出了“微服务”架构,它是一种全新的架构风格。
当然,这里只是简单的演示一下微服务架构,实际开发中肯定是比这个复杂得多的。
可见,采用微服务架构,更加能够应对当今时代下的种种考验,传统项目的开发模式,需要进行架构上的升级。
前面我们介绍了微服务架构的优点,那么同样的,这些优点的背后也存在着诸多的问题:
所以,为了更好地解决这些问题,SpringCloud正式登场。
SpringCloud是Spring提供的一套分布式解决方案,集合了一些大型互联网公司的开源产品,包括诸多组件,共同组成SpringCloud框架。并且,它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、熔断机制、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
由于中小型公司没有独立开发自己的分布式基础设施的能力,使用SpringCloud解决方案能够以最低的成本应对当前时代的业务发展。
可以看到,SpringCloud整体架构的亮点是非常明显的,分布式架构下的各个场景,都有对应的组件来处理,比如基于Netflix(奈飞)的开源分布式解决方案提供的组件:
当然,这里只是进行简单的了解即可,实际上微服务的玩法非常多,我们后面的学习中将会逐步进行探索。
那么首先,我们就从注册中心开始说起。
官方文档:https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/
小贴士: 各位小伙伴在学习的过程中觉得有什么疑惑的可以直接查阅官方文档,我们会在每一个技术开始之前贴上官方文档的地址,方便各位进行查阅,同时在我们的课程中并不一定会完完整整地讲完整个框架的内容,有关详细的功能和使用方法文档中也是写的非常清楚的,感兴趣的可以深入学习哦。
现在我们重新设计一下之前的图书管理系统项目,将原有的大型(也许 项目进行拆分,注意项目拆分一定要尽可能保证单一职责,相同的业务不要在多个微服务中重复出现,如果出现需要借助其他业务完成的服务,那么可以使用服务之间相互调用的形式来实现(之后会介绍):
那么既然要将单体应用拆分为多个小型服务,我们就需要重新设计一下整个项目目录结构,这里我们就创建多个子项目,每一个子项目都是一个服务,这样由父项目统一管理依赖,就无需每个子项目都去单独管理依赖了,也更方便一点。
我们首先创建一个普通的SpringBoot项目:
然后不需要勾选任何依赖,直接创建即可,项目创建完成并初始化后,我们删除父工程的无用文件,只保留必要文件,像下面这样:
接着我们就可以按照我们划分的服务,进行子工程创建了,创建一个新的Maven项目,注意父项目要指定为我们一开始创建的的项目,子项目命名随意:
子项目创建好之后,接着我们在子项目中创建SpringBoot的启动主类:
接着我们点击运行,即可启动子项目了,实际上这个子项目就一个最简单的SpringBoot web项目,注意启动之后最下方有弹窗,我们点击"使用 服务",这样我们就可以实时查看当前整个大项目中有哪些微服务了:
接着我们以同样的方法,创建其他的子项目,注意我们最好将其他子项目的端口设置得不一样,不然会导致端口占用,我们分别为它们创建application.yml
文件:
接着我们来尝试启动一下这三个服务,正常情况下都是可以直接启动的:
可以看到它们分别运行在不同的端口上,这样,就方便不同的程序员编写不同的服务了,提交当前项目代码时的冲突率也会降低。
接着我们来创建一下数据库,这里还是老样子,创建三个表即可,当然实际上每个微服务单独使用一个数据库服务器也是可以的,因为按照单一职责服务只会操作自己对应的表,这里UP主比较穷,就只用一个数据库演示了:
创建好之后,结果如下,一共三张表,各位可以自行添加一些数据到里面,这就不贴出来了:
如果各位嫌麻烦的话可以下载.sql
文件自行导入。
接着我们来稍微写一点业务,比如用户信息查询业务,我们先把数据库相关的依赖进行导入,这里依然使用Mybatis框架,首先在父项目中添加MySQL驱动和Lombok依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
由于不是所有的子项目都需要用到Mybatis,我们在父项目中只进行版本管理即可:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
</dependencyManagement>
接着我们就可以在用户服务子项目中添加此依赖了:
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
接着添加数据源信息(UP用到是阿里云的MySQL云数据库,各位注意修改一下数据库地址):
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://cloudstudy.mysql.cn-chengdu.rds.aliyuncs.com:3306/cloudstudy
username: test
password: 123456
接着我们来写用户查询相关的业务:
@Data
public class User {
int uid;
String name;
String sex;
}
@Mapper
public interface UserMapper {
@Select("select * from DB_USER where uid = #{uid}")
User getUserById(int uid);
}
public interface UserService {
User getUserById(int uid);
}
@Service
public class UserServiceImpl implements UserService {
@Resource
UserMapper mapper;
@Override
public User getUserById(int uid) {
return mapper.getUserById(uid);
}
}
@RestController
public class UserController {
@Resource
UserService service;
//这里以RESTFul风格为例
@RequestMapping("/user/{uid}")
public User findUserById(@PathVariable("uid") int uid){
return service.getUserById(uid);
}
}
现在我们访问即可拿到数据:
同样的方式,我们完成一下图书查询业务,注意现在是在图书管理微服务中编写(别忘了导入Mybatis依赖以及配置数据源):
@Data
public class Book {
int bid;
String title;
String desc;
}
@Mapper
public interface BookMapper {
@Select("select * from DB_BOOK where bid = #{bid}")
Book getBookById(int bid);
}
public interface BookService {
Book getBookById(int bid);
}
@Service
public class BookServiceImpl implements BookService {
@Resource
BookMapper mapper;
@Override
public Book getBookById(int bid) {
return mapper.getBookById(bid);
}
}
@RestController
public class BookController {
@Resource
BookService service;
@RequestMapping("/book/{bid}")
Book findBookById(@PathVariable("bid") int bid){
return service.getBookById(bid);
}
}
同样进行一下测试:
这样,我们一个完整项目的就拆分成了多个微服务,不同微服务之间是独立进行开发和部署的。
前面我们完成了用户信息查询和图书信息查询,现在我们来接着完成借阅服务。
借阅服务是一个关联性比较强的服务,它不仅仅需要查询借阅信息,同时可能还需要获取借阅信息下的详细信息,比如具体那个用户借阅了哪本书,并且用户和书籍的详情也需要同时出现,那么这种情况下,我们就需要去访问除了借阅表以外的用户表和图书表。
但是这显然是违反我们之前所说的单一职责的,相同的业务功能不应该重复出现,但是现在由需要在此服务中查询用户的信息和图书信息,那怎么办呢?我们可以让一个服务去调用另一个服务来获取信息。
这样,图书管理微服务和用户管理微服务相对于借阅记录,就形成了一个生产者和消费者的关系,前者是生产者,后者便是消费者。
现在我们先将借阅关联信息查询完善了:
@Data
public class Borrow {
int id;
int uid;
int bid;
}
@Mapper
public interface BorrowMapper {
@Select("select * from DB_BORROW where uid = #{uid}")
List<Borrow> getBorrowsByUid(int uid);
@Select("select * from DB_BORROW where bid = #{bid}")
List<Borrow> getBorrowsByBid(int bid);
@Select("select * from DB_BORROW where bid = #{bid} and uid = #{uid}")
Borrow getBorrow(int uid, int bid);
}
现在有一个需求,需要查询用户的借阅详细信息,也就是说需要查询某个用户具体借了那些书,并且需要此用户的信息和所有已借阅的书籍信息一起返回,那么我们先来设计一下返回实体:
@Data
@AllArgsConstructor
public class UserBorrowDetail {
User user;
List<Book> bookList;
}
但是有一个问题,我们发现User和Book实体实际上是在另外两个微服务中定义的,相当于当前项目并没有定义这些实体类,那么怎么解决呢?
因此,我们可以将所有服务需要用到的实体类单独放入另一个一个项目中,然后让这些项目引用集中存放实体类的那个项目,这样就可以保证每个微服务的实体类信息都可以共用了:
然后只需要在对应的类中引用此项目作为依赖即可:
<dependency>
<groupId>com.example</groupId>
<artifactId>commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
之后新的公共实体类都可以在commons
项目中进行定义了,现在我们接着来完成刚刚的需求,先定义接口:
public interface BorrowService {
UserBorrowDetail getUserBorrowDetailByUid(int uid);
}
@Service
public class BorrowServiceImpl implements BorrowService{
@Resource
BorrowMapper mapper;
@Override
public UserBorrowDetail getUserBorrowDetailByUid(int uid) {
List<Borrow> borrow = mapper.getBorrowsByUid(uid);
//那么问题来了,现在拿到借阅关联信息了,怎么调用其他服务获取信息呢?
}
}
需要进行服务远程调用我们需要用到RestTemplate
来进行:
@Service
public class BorrowServiceImpl implements BorrowService{
@Resource
BorrowMapper mapper;
@Override
public UserBorrowDetail getUserBorrowDetailByUid(int uid) {
List<Borrow> borrow = mapper.getBorrowsByUid(uid);
//RestTemplate支持多种方式的远程调用
RestTemplate template = new RestTemplate();
//这里通过调用getForObject来请求其他服务,并将结果自动进行封装
//获取User信息
User user = template.getForObject("http://localhost:8082/user/"+uid, User.class);
//获取每一本书的详细信息
List<Book> bookList = borrow
.stream()
.map(b -> template.getForObject("http://localhost:8080/book/"+b.getBid(), Book.class))
.collect(Collectors.toList());
return new UserBorrowDetail(user, bookList);
}
}
现在我们再最后完善一下Controller:
@RestController
public class BorrowController {
@Resource
BorrowService service;
@RequestMapping("/borrow/{uid}")
UserBorrowDetail findUserBorrows(@PathVariable("uid") int uid){
return service.getUserBorrowDetailByUid(uid);
}
}
在数据库中添加一点借阅信息,测试看看能不能正常获取(注意一定要保证三个服务都处于开启状态,否则远程调用会失败):
可以看到,结果正常,没有问题,远程调用成功。
这样,一个简易的图书管理系统的分布式项目就搭建完成了,这里记得把整个项目压缩打包备份一下,下一章学习SpringCloud Alibaba也需要进行配置。