环境准备:
1. 一台虚拟机,部署好centos7操作系统、安装好docker
2. 使用docker安装mysql数据库且启动mysql容器
3. IDEA配置的JDK版本是11
4. 前端代码启动Nginx
一、单体架构和微服务的区别?
1. 单体架构
将业务的所有功能集中在一个项目中开发,打成一个包部署。
优点:
架构简单、部署成本低
缺点:
- 团队协作成本高(多人在一个项目上提交代码容易造成很多的代码冲突,每天解决代码需要很久)
- 系统发布效率低(当代码体量过大时,重新发布需要耗时长)
- 系统可用性差(假如项目中有某些并发量很大的功能,还有一些访问量不大但是很重要的功能,例如:付款。服务器资源是有限的,一些功能占用了大量的资源,会影响别的功能的正常运转)使用apache-jmeter模拟测试
总结: 单体架构适合开发功能相对简单,规模较小的项目。
2. 微服务
微服务架构,是服务化思想指导下的一套最佳实践架构方案。服务化,就是把单体架构中的功能模块拆分为多个独立项目。
3. SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。 SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
二、微服务拆分
1. 服务拆分原则
1.1 什么时候拆分?
创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分。
确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦。
1.2 怎么拆分
从拆分目标来说,要做到:
- 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
- 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。
从拆分方式来说,一般包含两种方式:
- 纵向拆分:按照业务模块来拆分
- 横向拆分:抽取公共服务,提高复用性
2. 服务拆分
工程结构有两种:
- 独立Project
- Maven聚合(每个module以后是要分别打包部署的)
3. 远程调用
当存在业务耦合的情况时,比如购物车模块需要调用商品模块的接口服务,但又不在一个模块中,这个时候就需要了解远程调用了!
思考一下:前端访问后端,不也是两个服务之间的交互,是怎么实现的呢?
从控制台可以看到,前后端的交互使用的是http协议。那么后端微服务之间。。。。。
Spring给我们提供了一个RestTemplate工具,可以方便的实现Http请求的发送。使用步骤如下:
1)注入RestTemplate到Spring容器
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
2)发起远程调用
//@RequiredArgsConstructor
//private final RestTemplate restTemplate;
//@RequiredArgsConstructor和final配合使用,只为必要的参数建构造函数
// 2.查询商品
// List<ItemDTO> items = itemService.queryItemByIds(itemIds);
// 2.1 利用restTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtil.join(itemIds, ","))
);
// 2.2 解析响应
if (!response.getStatusCode().is2xxSuccessful()) {
//查询失败,直接结束
return;
}
List<ItemDTO> items = response.getBody();
测试了一下,这个 跨服务调用ok了。
三、服务治理
1. 服务远程调用存在的问题?
- 服务调用者在写代码时,事先不知道服务提供者的地址,只有服务提供者服务启动,地址信息才会暴露给服务调用者
- 地址这个事儿后续可以知道。服务提供者部署在一台服务器上,访问压力肯定超级大,所以会进行集群部署,那么服务调用者返回时,应该配置哪个地址进行访问呢
- 地址也可以配置多个,然后服务调用者获取拼接访问地址也可。但是假如服务提供者其中一台服务器挂了(人只有在访问异常时才会知道是服务器挂了),服务调用者还跟之前一样的策略(比如随机访问固定的那几台服务器),是有概率访问到故障机的;或者服务提供者那边又重新启动了几台新的机器,除非人为奔走相告,服务调用者这边是感知不到的。那这运维人员发现故障再进行调整,或者调整完之后再传递信息给服务调用者,是有时间差的,可用性太差。
2. 注册中心的原理
cart-service它既是服务调用者,同时在某些情景下,它也是服务提供者;各服务之间只管发布自己、便于服务调用者寻找适合需求自己的服务,那么注册中心就是起一个中介的作用,你可以提供服务,就注册在我这里,注册表里会记录所有服务信息。这时服务调用者就从注册中心选择服务进行访问,但是应该访问哪个呢?访问时由负载均衡提供的策略指导。
在注册中心的服务会定期向注册中心发请求,汇报自己的健康状况,名为心跳续约。当某个服务挂掉之后,注册中心会感知到,在提供服务列表会剔除这一服务,更新注册表,并将这一情况推送给服务调用者,名为推送变更。
服务治理中的三个角色分别是什么?
- 服务提供者:暴露服务接口,供其它服务调用
- 服务消费者:调用其它服务提供的接口
- 注册中心:记录并监控微服务各实例状态,推送服务变更信息
消费者如何知道提供者的地址?
服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息
消费者如何得知服务状态变更?
服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者
当提供者有多个实例时,消费者该选择哪一个?
消费者可以通过负载均衡算法,从多个实例中选择一个
3. Nacos注册中心
Nacos是目前国内企业中占比最多的注册中心组件。它是阿里巴巴的产品,目前已经加入SpringCloudAlibaba中。
3.1 准备Nacos数据库
我们基于Docker来部署Nacos的注册中心,首先我们要准备MySQL数据库表,用来存储Nacos的数据。由于是Docker部署,需要将资料中的SQL文件导入到Docker中的MySQL容器中
3.2 修改nacos/custom.env
文件
其中的nacos/custom.env
文件中,有一个MYSQL_SERVICE_HOST也就是mysql地址,需要修改为你自己的虚拟机IP地址:
将nacos目录和nacos镜像nacos.tar上传到虚拟机/root下
加载镜像nacos.tar
docker load -i /root/nacos.tar
查看镜像
docker images
3.3 启动nacos
docker run -d \
--name nacos \
--env-file /root/nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
3.4 访问nacos
账号:nacos;密码:nacos
4. 服务注册
4.1.添加依赖
在item-service
的pom.xml
中添加依赖:
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
4.2.配置Nacos
spring:
cloud:
nacos:
server-addr: localhost:8848 //配置虚拟机地址
4.3.启动服务实例
启动两个服务实例,在nacos网页上验证:
5. 服务发现和负载均衡
消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册是一样,后面再加上服务调用即可:
5.1 服务发现以及负载均衡-随机
private final DiscoveryClient discoveryClient;
/*注册中心查找实例方式*/
//2.1根据服务名称获取服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
if (CollUtil.isEmpty(instances)) {
return;
}
//2.2手写负载均衡,从实例列表中挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
//2.3利用restTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
instance.getUri() + "/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtil.join(itemIds, ","))
);
// 2.2 解析响应
if (!response.getStatusCode().is2xxSuccessful()) {
//查询失败,直接结束
return;
}
List<ItemDTO> items = response.getBody();
四、OpenFeign
1. 入门
OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送
官方地址:https://github.com/OpenFeign/feign
OpenFeign已经被SpringCloud自动装配,实现起来非常简单:
1)引入依赖,包括OpenFeign和负载均衡组件SpringCloudLoadBalancer
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2)启用OpenFeign
在cart-service
的CartApplication
启动类上添加注解,启动OpenFeign功能:
3)编写OpenFeign客户端
/**
* @Author: EstellaQ
* @Date: 2025/4/19 16:02
* @Description: 商品模块feign远程调用
**/
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO