一、什么是SpringCloud
基本概念
Spring Cloud 是一个开源的分布式系统开发框架,它基于 Spring Boot 构建,并提供了一系列工具和组件,用于构建和管理分布式应用程序和微服务架构。
SpringCloud的特点
SpringCloud能够解决微服务架构带来的一系列挑战,提供一个工具包来开发分布式系统
功能包含:
分布式配置中心 Config、Nacos
服务注册和发现 Eureka、Nacos、Zookeeper、Consul
路由 Gateway、Zuul
服务之间的调用 Feign、Dubbo
负载均衡 Ribbon、LoadBalancer
熔断器 Hystrix、Sentinel
全局锁
集群状态管理
分布式消息 Bus
优点:
1.代码耦合度较低,不会影响其他模块的开发
2.极大的减轻了团队开发成本,可并行开发,不用过多关注其他人怎么开发
3.配置比较简单,基本用注解就能实现,不能使用过多的配置文件
4.微服务操作,实现跨平台的,可以使用不同的语言开发
5.每个微服务可以使用自己的数据库,也可以使用公共的数据库
可简单总结为:低耦合,低成本,低配置,跨平台,不同库。
缺点:
微服务过多,治理成本高,不利于维护系统
分布式系统开发的成本高(容错,分布式事务等)对团队挑战大
优点:
1)服务的独立部署
每个服务都是一个独立的项目,可以独立部署,不依赖于其他服务,耦合性低。
2)服务的快速启动
拆分之后服务启动的速度必然要比拆分之前快很多,因为依赖的库少了,代码量也少了。
3)更加适合敏捷开发
敏捷开发以用户的需求进化为核心,采用迭代、循序渐进的方法进行。服务拆分可以快速发布新版本,修改哪个服务只需要发布对应的服务即可,不用整体重新发布。
4)职责专一,由专门的团队负责专门的服务
业务发展迅速时,研发人员也会越来越多,每个团队可以负责对应的业务线,服务的拆分有利于团队之间的分工。
5)服务可以动态按需扩容
当某个服务的访问量较大时,我们只需要将这个服务扩容即可。
6)代码的复用
每个服务都提供 REST API,所有的基础服务都必须抽出来,很多的底层实现都可以以接口方式提供。
缺点:
1)分布式部署,调用的复杂性高
单体应用的时候,所有模块之前的调用都是在本地进行的,在微服务中,每个模块都是独立部署的,通过 HTTP 来进行通信,
这当中会产生很多问题,比如网络问题、容错问题、调用关系等。
2)独立的数据库,分布式事务的挑战
每个微服务都有自己的数据库,这就是所谓的去中心化的数据管理。这种模式的优点在于不同的服务,可以选择适合自身业务的数据,
比如订单服务可以用 MySQL、评论服务可以用 Mongodb、商品搜索服务可以用 Elasticsearch。
缺点就是事务的问题了,目前最理想的解决方案就是柔性事务中的最终一致性,后面的章节会给大家做具体介绍。
3)测试的难度提升
服务和服务之间通过接口来交互,当接口有改变的时候,对所有的调用方都是有影响的,这时自动化测试就显得非常重要了,如果要靠人工一个个接口去测试,那工作量就太大了。这里要强调一点,就是 API 文档的管理尤为重要。
4)运维难度的提升
在采用传统的单体应用时,我们可能只需要关注一个 Tomcat 的集群、一个 MySQL 的集群就可以了,但这在微服务架构下是行不通的。当业务增加时,服务也将越来越多,服务的部署、监控将变得非常复杂,这个时候对于运维的要求就高了
二、 微服务架构和单体应用架构
微服务架构
微服务架构是一种将应用程序拆分为一组小型、独立的服务的架构模式。每个服务都具有自己的代码库、数据库和资源,并通过网络进行通信。每个服务都负责特定的功能或业务领域。
单体架构
单体应用架构是一种传统的软件架构模式,应用程序作为一个整体被构建、打包和部署。在单体应用中,所有的功能模块和业务逻辑都在同一个代码库中,并共享相同的数据库和资源。
两者的区别
一、架构组织方式:
微服务架构:应用程序以一组小型、独立的服务组织。每个服务都有自己的代码库、数据库和资源,并通过轻量级通信机制进行交互。
单体应用架构:应用程序作为一个整体被组织,所有的模块和功能共享同一个代码库、数据库和资源。
二、可伸缩性:
微服务架构:每个服务可以独立地进行水平扩展,只需对需要处理更多负载的服务进行扩展。
单体应用架构:整个应用必须进行垂直扩展,即增加整个应用的资源来处理更多负载。
三、开发和部署:
微服务架构:每个微服务可以由不同的团队开发和部署,独立进行测试和发布。
单体应用架构:整个应用由一个团队开发和部署,需要进行整体的集成测试和部署。
四、系统复杂性:
微服务架构:由于涉及多个独立的服务,系统的复杂性较高,需要更多的管理和协调。
单体应用架构:应用较为简单,系统复杂性较低。
五、技术灵活性:
微服务架构:每个微服务可以选择适合自身需求的技术和工具。
单体应用架构:由于整个应用耦合在一起,技术选型受限。
六、维护和修改:
微服务架构:对某个服务的修改和维护不会影响其他服务,每个服务可以独立开发和更新。
单体应用架构:修改和维护可能会影响整个应用,需要更多的协调和依赖管理。
三、案例
提供数据的服务叫服务提供者,使用数据的服务叫服务消费者
订单微服务能够查询订单信息,订单信息中包含商品数据,订单微服务要调用商品微服务完成查询
订单服务数据库为sys_order
-- 订单表
create table sys_order
(
id int primary key auto_increment,
product_id int not null,
count int not null,
user_id int not null,
time datetime
);
insert into sys_order(product_id,count,user_id,time)
values(1,1,1,'2022-11-21 13:11:12'),
(2,2,1,'2022-12-21 13:11:12'),
(3,1,1,'2022-10-21 13:11:12');
select * from sys_order;
商品服务的商品数据库sys_product
-- 商品表
create table sys_product
(
id int PRIMARY key auto_increment,
name varchar(50) not null,
price double not null,
type varchar(10) not null
);
insert into sys_product(name,price,type)
values('华为笔记本',5000,'电器'),
('百事可乐',5,'饮料'),
('Nike篮球鞋',800,'鞋子');
select * from sys_product;
-
编写商品服务,完成按商品id查询商品的功能
-
编写订单服务,完成按查询订单的功能
-
订单微服务调用商品微服务
@Configuration
public class RestTemplateConfig {
/**
* 创建RestTemplate交给容器
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
订单包含商品信息
@Data
@TableName("sys_order")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long productId;
private Long count;
private LocalDateTime time;
@TableField(exist = false)
private Product product;
}
订单service查询订单时远程调用商品服务
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderMapper orderMapper;
@Override
public Order getOrderById(Long id) {
Order order = orderMapper.selectById(id);
//调用商品微服务获得商品信息
ResponseEntity<Product> entity = restTemplate.getForEntity("http://localhost:8002/product/" + order.getProductId(), Product.class);
order.setProduct(entity.getBody());
return order;
}
}
四、Eureka注册中心的使用
1) 服务提供者将自己的IP和端口注册到注册中心上
2) 服务提供者每隔一段时间向注册中心发送心跳包
3) 服务消费者调用提供者之前,先向注册中心查询提供者的IP和端口
4) 获得服务清单中的IP和端口后,消费者调用提供者
5) 服务提供者的IP和端口改变后,通过心跳机制更新注册中心上的服务清单
修改父项目pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.blb</groupId>
<artifactId>springcloud_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloud_demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
<spring.cloud-version>Hoxton.SR8</spring.cloud-version>
</properties>
<!--固定SpringCloud依赖版本-->
<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>
</dependencies>
</dependencyManagement>
创建Eureka服务器项目
pom修改
1) 继承父项目
<parent>
<groupId>com.blb</groupId>
<artifactId>springcloud_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
2) 引入服务器依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3) 配置文件
server.port=8000
# 服务器域名
eureka.instance.hostname=127.0.0.1
# 设置不拉取服务清单
eureka.client.fetch-registry=false
# 设置不注册当前服务
eureka.client.register-with-eureka=false
# 定义注册服务的地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
4) 启动类加 @EnableEurekaServer
配置Eureka客户端
1) 继承父项目
2) 引入客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
3) 配置文件
# 服务名称
spring.application.name=xxxx
# 设置拉取服务清单
eureka.client.fetch-registry=true
# 设置注册当前服务
eureka.client.register-with-eureka=true
# 定义注册服务的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka
4) 启动类加 @EnableEurekaClient或 @EnableDiscoveryClient
5) 在RestTemplate配置类方法上加@LoadBalanced
6) 调用服务时修改
//调用商品微服务获得商品信息 将IP和端口写死 服务提供者ip和端口发生变化,调用出错
//改用Eureka后,调用时使用服务注册名称调用
ResponseEntity<Product> entity = restTemplate.getForEntity("http://product-service/product/" + order.getProductId(),
Product.class);
Eureka自我保护机制
心跳机制:客户端注册到Eureka服务器后,每隔30s发送心跳包给服务器,如果90s没有发送心跳包,服务器就会把客户端从服务清单剔除
自我保护机制:网络可能出现波动,Eureka服务器发现15分钟内所有客户端正常心跳比例低于85%,Eureka就会把服务清单保护起来不剔除,网络恢复正常后,服务调用就能正常执行。
开发阶段可以关闭自我保护机制,上线阶段一般都要开启
# 关闭自我保护机制
eureka.server.enable-self-preservation=false
可以配置多台Eureka服务器形成集群,相互注册相互备份,提高注册中心的可用性
application-dev.properties
server.port=8011
spring.application.name=eureka-8011
# 服务器域名
eureka.instance.hostname=127.0.0.1
# 设置从其它eureka拉取服务清单
eureka.client.fetch-registry=true
# 设置注册当前服务到其它eureka
eureka.client.register-with-eureka=true
# 定义注册服务的地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8012/eureka
# 关闭自我保护机制
eureka.server.enable-self-preservation=false
application-test.properties
server.port=8012
spring.application.name=eureka-8012
# 服务器域名
eureka.instance.hostname=127.0.0.1
# 设置拉取服务清单
eureka.client.fetch-registry=true
# 设置注册当前服务到其它eureka
eureka.client.register-with-eureka=true
# 定义注册服务的地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8011/eureka
# 关闭自我保护机制
eureka.server.enable-self-preservation=false
服务提供者和消费者注册到集群上
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8012/eureka,http://127.0.0.1:8011/eureka