SpringCloud是由Spring提供的一套能够快速搭建微服务架构程序的框架集
框架集表示SpringCloud不是一个框架,而是很多框架的统称
SpringCloud是为了搭建微服务架构的程序而出现的
有人将SpringCloud称之为"Spring全家桶",广义上指代所有Spring的产品
从内容提供者角度
什么Nacos
安装启动Nacos
https://github.com/alibaba/nacos/releases/download/1.4.3/nacos-server-1.4.3.zip
将压缩包解压(注意不要有中文路径或空格)
打开解压得到的文件夹后打开bin目录
cmd结尾的文件是windows版本的
在当前资源管理器地址栏输入cmd
G:\pgm\nacos\bin>startup.cmd -m standalone
-m是设置启动方式参数,standalone翻译为标准的孤独的,意思是单机模式标准运行
验证Nacos的运行状态
创建csmall项目
添加依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
添加配置
spring:
application:
# 当前Springboot项目的名称,用作注册中心服务的名称
name: nacos-business
cloud:
nacos:
discovery:
# 定义nacos运行的路径
server-addr: localhost:8848
在保证nacos已经启动的前提下,我们启动business项目
临时实例(默认)
持久化实例(永久实例)
我们可以通过设置属性来确定它是临时还是永久
cloud:
nacos:
discovery:
# ephemeral设置当前项目启动时注册到nacos的类型 true(默认):临时实例 false:永久实例
ephemeral: true
临时实例
默认情况下,启动服务后,每隔5秒会向nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息
Nacos收到这个"心跳包"如果发现这个服务的信息不在注册列表中,就进行注册,如果这个服务的信息在注册列表中就表明这个服务还是健康的
如果Nacos15秒内没接收到某个服务的心跳包,Nacos会将这个服务标记为不健康的状态
如果30秒内没有接收到这个服务的心跳包,Nacos会将这个服务从注册列表中剔除
这些时间都是可以通过配置修改的
持久化实例(永久实例)
持久化实例启动时向nacos注册,nacos会对这个实例进行持久化处理
心跳包的规则和临时实例一致,只是不会将该服务从列表中剔除
什么是RPC
RPC是Remote Procedure Call的缩写 翻译为:远程过程调用
目标是为了实现两台(多台)计算机\服务器,互相调用方法\通信的解决方案
RPC的概念主要定义了两部分内容
什么是Dubbo
Dubbo的协议支持
Dubbo服务的注册与发现
服务的提供者,指服务的拥有者---------生产者(provider)
服务的消费者,指服务的调用者---------消费者(consumer)
注册中心,在Dubbo中,远程调用依据是服务的提供者在Nacos中注册的服务名称
常见面试题:Dubbo的注册发现流程
1.首先服务的提供者启动服务到注册中心注册,包括各种ip端口信息,Dubbo会同时注册该项目提供的远程调用的方法
2.服务的消费者(使用者)注册到注册中心,订阅发现
3.当有新的远程调用方法注册到注册中心时,注册中心会通知服务的消费者有哪些新的方法,如何调用的信息
4.RPC调用,在上面条件满足的情况下,服务的调用者无需知道ip和端口号,只需要服务名称就可以调用到服务提供者的方法
负载均衡
在实际开发中,一个服务基本都是集群模式的,也就是多个功能相同的项目在运行,这样才能承受更高的并发,这时一个请求到这个服务,就需要确定访问哪一个服务器…
Dubbo框架内部支持负载均衡算法,能够尽可能的让请求在相对空闲的服务器上运行,我们要实现设置好负载均衡的策略算法,并设置好每个服务器的运行权重,才能更好的实现负载均衡的效果.
Loadbalance:就是负载均衡的意思
Dubbo内置负载均衡策略算法(Dubbo内置4种负载均衡算法)
random loadbalance:随机分配策略(默认)
round Robin Loadbalance:权重平均分配
leastactive Loadbalance:活跃度自动感知分配
consistanthash Loadbalance:一致性hash算法分配
Dubbo实现微服务调用
Dubbo生产者消费者相同的配置
pom文件添加dubbo依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-dubboartifactId>
dependency>
yml文件配置dubbo信息
dubbo:
protocol:
port: -1 # 设置Dubbo服务调用的端口 设置-1能够实现动态自动设置合适端口,生成规则是从20880开始递增
name: dubbo # 设置端口名称,一般固定就叫dubbo
registry:
address: nacos://localhost:8848 # 配置当前Dubbo注册中心的类型和地址
consumer:
check: false # 设置为false表示当前项目启动时,不检查要调用的远程服务是否可用,避免报错
Dubbo生产者消费者不同的配置
下载Seata
什么是Seata
为什么需要Seata
事务的4个特性:ACID特性
- 原子性
- 一致性
- 隔离性
- 永久性
我们再业务中,必须保证数据库操作的原子性,也就是当前业务的所有数据库操作要么都成功,要么都失败.之前我们使用Spring声明式事务来解决本地的事务问题,但是现在是微服务环境,一个业务可能涉及多个模块的数据库操作,这种情况就需要专门的微服务状态下解决事务问题的"分布式事务"解决方案.
Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案
Seata的运行原理(AT模式)
上面结构是比较典型的远程调用结构
如果account操作数据库失败需要让order模块和storage模块撤销(回滚)操作
声明式事务不能完成这个操作,需要Seata来解决,解决模型如下
Seata构成部分包含
AT(自动)模式完成分布式事务的解决
使用Seata
配置Seata(生产者)
添加依赖()
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
配置文件yml()
seata:
tx-service-group: csmall_group #定义分组名称
service:
vgroup-mapping:
csmall_group: default # 使用seata默认事务配置
grouplist:
default: localhost:8091 # 8091是seata默认的地址
注意同一个事务必须在同一个tx-service-group中,同时指定相同的seata地址和端口
消费者
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
dependency>
seata:
tx-service-group: csmall_group #定义分组名称
service:
vgroup-mapping:
csmall_group: default # 使用seata默认事务配置
grouplist:
default: localhost:8091 # 8091是seata默认的地址
seata标记事务的开始有一个专用的注解@GlobalTransactional,TM(事务管理器)的业务逻辑层(service)方法上添加注解**@GlobalTransactional**
启动seata
seata也是java开发的,启动方式和nacos很像,只是启动命令不同,解压后路径不要用中文,不要用空格
在路径上输入cmd进入dos窗口
G:\pgm\seata\seata-server-1.4.2\bin>seata-server.bat -h 127.0.0.1 -m file
在windows系统中运行seata可能出现不稳定的情况,重启seata即可解决
什么是Sentinel
为什么需要Sentinel
基本配置和限流效果
添加pom依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
修改yml配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 # 配置Sentinel仪表台的位置
port: 8721 # 真正执行限流的端口也要设置一下,注意这个端口其他微服务项目不能相同
Sentinel限流针对控制层方法@SentinelResource(“减少库存方法(控制器)”)
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存业务")
// @SentinelResource标记的方法会被Sentinel监控
// ()里面的内容是这个监控的名称,我们可以在"仪表台"中看到
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
@SentinelResource("减少库存方法(控制器)")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
流控与降级
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存业务")
// @SentinelResource标记的方法会被Sentinel监控
// value的值是这个监控的名称,我们可以在"仪表台"中看到
// blockHandler的值指定了请求被限流时运行的方法名称
@SentinelResource(value = "减少库存方法(控制器)",blockHandler = "blockError",fallback = "fallbackError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
// 生成随机出触发降级流程
if(Math.random()<0.5){
throw new
CoolSharkServiceException(ResponseCode.INTERNAL_SERVER_ERROR,"异常");
}
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
// 这个方法是Sentinel注解中fallback属性指定的降级方法
// 当前控制器方法运行发生异常时,Sentinel会运行下面的降级方法
// 降级方法中,可以不直接结束请求,而去运行一些代替代码或者补救措施
// 让用户获得最低限度的响应
public JsonResult fallbackError(StockReduceCountDTO stockReduceCountDTO){
return JsonResult.failed(ResponseCode.BAD_REQUEST,"因为运行异常,服务降级");
}
blockHandler和fallback的区别
两者都是不能正常调用资源返回值的顶替处理逻辑.
blockHander只能处理BlockException 流控限制之后的逻辑.
fallback处理的是资源调用异常的降级逻辑.
早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎
这些框架和Spring Cloud Alibaba的对应关系我们要知道
Nacos对应Eureka 都是注册中心
Dubbo对应ribbon+feign都是实现微服务间调用
Sentinel对应Hystrix都是项目限流熔断降级组件
Gateway对应zuul都是项目的网关
Gateway不是阿里巴巴的而是Spring提供的
什么是网关
“网关"网是网络,关是关口\关卡,关口\关卡的意思就是"统一入口”,网关:就是网络中的统一入口,
程序中的网关就是微服务项目提供的外界所有请求统一访问的微服务项目
因为提供了统一入口之后,方便对所有请求进行统一的检查和管理
网关的主要功能有
Spring Gateway简介
简单网关演示
网关是一个我们创建的项目,不是一个需要安装的软件,网关也是当前微服务项目的一员,也要注册到Nacos,所以保证Nacos的运行,运行之前,我们看一下网关演示项目已经存在的基本结构,beijing和shanghai是编写好的两个项目,gateway没有编写yml文件配置,要想实现网关的路由效果需要修改yml文件如下
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes: # gateway开始配置路由信息
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
# 如果java访问这个数字元素的方式:spring.cloud.gateway.routes[0].predicates[0]
# routes属性实际上是一个数组,yml文件中出现 "- ...."配置时表示当前配置时一个数组元素
- id: gateway-beijing # 这个配置指定这个路由的名称,这个名称和其他任何位置没有关联
# 只需要注意不能再和其他路由名称重复
# uri设置路由的目标
# lb是LoadBalance(负载均衡)的缩写,beijing是注册到nacos的服务名称
uri: lb://beijing
# 我们需要设置一个条件,当访问路径满足特定条件是,使用当前路由规则
predicates:
# predicates翻译为断言,所谓断言就是判断一个条件是否满足
# Path 是路径断言,意思是满足路径为XXX时使用这个路由
- Path=/bj/**
# http://localhost:9000/bj/show 会路由到 9001/bj/show
内置断言
断言就是判断一个条件,如果条件满足就执行某个操作
predicates就是断言的意思
Path就是内置断言中的一种,指访问的路径是否满足条件
除了路径断言之外,还有很多内置断言常见的内置断言列表
时间相关
after,before,between
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- After=2022-06-24T15:30:30.999+08:00[Asia/Shanghai]
ZonedDateTime.now()
内置过滤器
Gateway还提供的内置过滤器
内置过滤器允许我们在路由请求到目标资源的同时,对这个请求进行一些加工或处理
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- Query=name
filters:
- AddRequestParameter=age,80
动态路由
如果项目微服务数量多,
那么gateway项目yml文件配置也会越来越冗余,维护的工作量也会越来越大
所谓我们希望能够根据固定特征自动的路由到每个微服务模块
这个功能就是SpringGateway的动态路由功能
只需要在配置文件中配置开启动态路由功能即可
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 开启Spring Gateway的动态路由功能
# 规则是根据注册到Nacos的项目名称作为路径的前缀,就可以访问到指定项目了
enabled: true
创建gateway网关子项目
子项目pom文件为
4.0.0
cn.tedu
csmall
0.0.1-SNAPSHOT
cn.tedu
gateway
0.0.1-SNAPSHOT
gateway
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-loadbalancer
org.springframework.cloud
spring-cloud-starter-gateway
com.github.xiaoymin
knife4j-spring-boot-starter
application.yml文件内容如下
spring:
application:
name: gateway-server
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 开启网关动态路由
enabled: true
main:
web-application-type: reactive
server:
port: 10000
knife4j网关配置
在config包下
@Component
public class SwaggerProvider implements SwaggerResourcesProvider {
/**
* 接口地址
*/
public static final String API_URI = "/v2/api-docs";
/**
* 路由加载器
*/
@Autowired
private RouteLocator routeLocator;
/**
* 网关应用名称
*/
@Value("${spring.application.name}")
private String applicationName;
@Override
public List<SwaggerResource> get() {
//接口资源列表
List<SwaggerResource> resources = new ArrayList<>();
//服务名称列表
List<String> routeHosts = new ArrayList<>();
// 获取所有可用的应用名称
routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
.filter(route -> !applicationName.equals(route.getUri().getHost()))
.subscribe(route -> routeHosts.add(route.getUri().getHost()));
// 去重,多负载服务只添加一次
Set<String> existsServer = new HashSet<>();
routeHosts.forEach(host -> {
// 拼接url
String url = "/" + host + API_URI;
//不存在则添加
if (!existsServer.contains(url)) {
existsServer.add(url);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setUrl(url);
swaggerResource.setName(host);
resources.add(swaggerResource);
}
});
return resources;
}
}
controller包下
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerController {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerController(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
filter包下
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
private static final String URI = "/v2/api-docs";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path,URI )) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}