SpringCloud 是目前国内使用最广泛的微服务框架。SpringCloud 集成了各种微服务功能组件,并基于 SpringBoot 实现了这些组件的自动装配,从而提供了良好的开箱即用体验。官网地址:https://spring.io/projects/spring-cloud
文章目录
(2)application.yml中添加nacos地址配置:
前言
之前我们学习的项目都是单体架构项目,可以满足小型项目或传统项目的开发。而在互联网时代,越来越多的一线互联网公司都在使用微服务技术。从谷歌搜索指数来看,国内从自2016年底开始,微服务热度突然暴涨。
一、含义
微服务是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。
二、单体架构&微服务
1.什么是单体架构
含义:单体架构就是将所有的项目集中在一个项目里开发,打成一个jar包部署
优点:
- 架构简单
- 部署成本低
缺点:
- 团队协作成本高
- 系统发布效率低
- 系统可用性差
2.什么是微服务架构
微服务架构是服务化思想指导下的一套最佳实践架构方案。服务化就是把单体架构中的功能模块拆分为多个独立项目。
3.微服务的拆分原则
高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。
4.微服务的拆分方式
纵向拆分:按照业务模块来拆分
横向拆分:抽取公共服务,提高复用性
三、远程调用---RestTemplate
Spring给我们提供了一个RestTemplate工具,可以方便的实现Http请求的发送。使用步骤如下:
1.注入RestTemplate到Spring容器
package com.hmall.cart.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RemoteCallConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2.发起远程调用
public <T> ResponseEntity<T> exchange(
String url, // 请求路径
HttpMethod method, // 请求方式
@Nullable HttpEntity<?> requestEntity, // 请求实体,可以为空
Class<T> responseType, // 返回值类型
Map<String, ?> uriVariables // 请求参数
)
注意:但是这种方法存在漏洞,如果恰巧远程调用的服务挂掉了那么当前方法的执行是不会通过的。
四、服务治理
1.服务治理中的三个角色
- 服务提供者:暴露服务接口,供其它服务调用
- 服务消费者:调用其它服务提供的接口
- 注册中心:记录并监控微服务各实例状态,推送服务变更信息
2.注册中心原理
流程如下:
-
服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
-
调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
-
调用者自己对实例列表负载均衡,挑选一个实例
-
调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
-
服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
-
当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
-
当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
-
当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
3.服务注册
(1)添加依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)application.yml中添加nacos地址配置:
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
4.服务发现
消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册是一样,后面再加上服务调用即可:
(1)添加依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)配置nacos地址
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
(3)服务发现
private final DiscoveryClient discoveryClient;
private void handleCartItems(List<CartVO> vos) {
// 1.根据服务名称,拉取服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
// 2.负载均衡,挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
// 3.获取实例的IP和端口
URI uri = instance.getUri();
// ...略
}
五、OpenFeign
1.快速入门
(1)引入依赖
在服务调用者的pom.xml中引入OpenFeign的依赖和LoadBalancer依赖:
<!--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
在服务调用者的启动类上添加 @EnableFeignClients 注解,表示启动OpenFeign
(3)编写OpenFeign客户端
在服务调用者中定义一个新的接口,编写Feign客户端。其中代码如下:
package com.hmall.cart.client;
import com.hmall.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient("item-service") //生命服务名称
public interface ItemClient {
@GetMapping("/items") //生命请求方式和请求路径
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
(4)使用FeignClient
最后在服务调用者的ServiceImpl中直接调用ItemClient方法
2.连接池---OKHttp
(1)引入依赖
在服务调用者的pom.xml中引入依赖:
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
(2)开启连接池
在服务i调用者的application.yml配置文件中开启Feign的连接池功能:
feign:
okhttp:
enabled: true # 开启OKHttp功能
重启服务,连接池就生效了。
3.最佳实践
在写项目的过程中有可能会发生一个情况,就是同一个接口有可能会被多个微服务调用。那么为了避免重复编码,可以把这些重复代码抽取到一个单独的模块中
(1)抽取Feign客户端
在项目下定义一个新的module并为它设置名称,其依赖如下:
<?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">
<parent>
<artifactId>hmall</artifactId>
<groupId>com.heima</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hm-api</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!--open feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- load balancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- swagger 注解依赖 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.6</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
(2)然后把需要用到的实体类放在一个包里
现在任何微服务想要调用其他微服务的直接,只需要引入这个模块的依赖即可,无需再自己编写Feign客户端了
(3)扫描包
接下来,我们在服务调用者的pom.xml中引入刚才创建的模块的依赖
<!--feign模块-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-api</artifactId>
<version>1.0.0</version>
</dependency>
如果重启项目报错的话,在启动类上添加@EnableFeignClients(basePackages = "OpenFeign客户端所在的包")
4.日志配置
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。
(1)OpenFeign的日志级别
-
NONE:不记录任何日志信息,这是默认值。
-
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
-
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
-
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
(2)定义日志级别
在刚在新建的模块下新建一个配置类,定义Feign的日志级别:
package com.hmall.api.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}
(3)配置
接下来,要让日志级别生效,还需要配置这个类。有两种方式:
-
局部生效:在某个FeignClient中配置,只对当前的FeignClient生效
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
-
全局生效:在@EnableFeiginClients中配置,仅对所有的FeignClient生效
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
六、网关
1.含义
就是网络的关口,负责请求的路由、转发、身份校验。
2.快速入门
(1)创建项目添加依赖
<?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">
<parent>
<artifactId>hmall</artifactId>
<groupId>com.heima</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hm-gateway</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!--common-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2)新建启动类
package com.hmall.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
(3)配置application.yml
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.150.101:8848
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
- Path=/items/**,/search/** # 这里是以请求路径作为判断规则
- id: cart
uri: lb://cart-service
predicates:
- Path=/carts/**
- id: user
uri: lb://user-service
predicates:
- Path=/users/**,/addresses/**
- id: trade
uri: lb://trade-service
predicates:
- Path=/orders/**
- id: pay
uri: lb://pay-service
predicates:
- Path=/pay-orders/**
(4)路由过滤
路由规则的定义语法如下:
spring:
cloud:
gateway:
routes:
- id: item
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
四个属性含义如下:
-
id
:路由的唯一标示 -
predicates
:路由断言,其实就是匹配条件 -
filters
:路由过滤条件,后面讲 -
uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
3.配置热更新
参考:【Nacos】配置管理、微服务配置拉取、实现配置热更新、多环境配置
七、服务保护
1.雪崩问题
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。
2.保护方案
(1)请求限流
请求限流:限制访问接口的请求的并发量,避免服务因流量激增出现故障。
(2)线程隔离
线程隔离:也叫做舱壁模式,模拟船舱隔板的防水原理。通过限定每个业务能使用的线程数量而将故障业务隔离,避免故障扩散。
线程隔离的思想来自轮船的舱壁模式:
轮船的船舱会被隔板分割为N个相互隔离的密闭舱,假如轮船触礁进水,只有损坏的部分密闭舱会
(3)服务熔断
服务熔断:由断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求。熔断期间,所有请求快速失败,全都走 fallback 逻辑。
3.服务保护技术 --- Sentinel
(1)安装
SpringCloud之Sentinel概述和安装及简单整合
(2)OpenFeign整合Sentinel
修改服务调用者的application.yml文件,开启Feign的sentinel功能:
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
八、分布式事务
1.含义
在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务。整个业务称为全局事务。
2.seata
(1)安装
Seata安装与使用以及部分常见问题(保姆级教程)
(2)架构
Seata 事务管理中有三个重要的角色:
TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器:管理分支事务,与 TC 交谈以注册分支事务和报告分支事务的状态
(3)XA 模式
XA 规范 是 X/Open 组织定义的分布式事务处理( DTP , Distributed Transaction Processing )标准, XA 规范 描述了全局的 TM 与局部的 RM 之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。 Seata 的 XA 模式如下:
(4)AT模式
AT模式同样是分阶段提交的事务模型,不过缺弥补了XA
模型中资源锁定周期过长的缺陷。
(5)AT 模式与 XA 模式最大的区别是什么?
• XA 模式一阶段不提交事务,锁定资源; AT 模式一阶段直接提交,不锁定资源。
• XA 模式依赖数据库机制实现回滚; AT 模式利用数据快照实现数据回滚。
• XA 模式强一致; AT 模式最终一致
总结
欢迎各位阅读并提出宝贵建议,本文旨在分享个人对于SpringCloud的学习总结