Spring Cloud
SpringCloud是由Spring提供的一套能够快速搭建微服务架构程序的框架集
框架集表示SpringCloud不是一个框架,而是很多框架的统称
SpringCloud是为了搭建微服务架构的程序而出现的
有人将SpringCloud称之为"Spring全家桶",广义上指代所有Spring的产品
SpringCloud的内容
从内容提供者角度
- Spring自己编写的框架和软件
- Netflix(奈非):早期提供了很多(全套)微服务架构组件
- alibaba(阿里巴巴):新版本SpringCloud推荐使用(正在迅速占领市场)
从功能上分类 - 微服务的注册中心
- 微服务间的调用
- 微服务的分布式事务
- 微服务的限流
- 微服务的网关
- …
Nacos(注册中心)
-
什么Nacos
-
Nacos是Spring Cloud Alibaba提供的一个软件 -
主要具有注册中心和配置中心的功能 -
微服务中所有项目都必须注册到注册中心才能成为微服务的一部分 -
注册中心和企业中的人力资源管理部门有相似 -
安装启动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的运行状态
- 打开浏览器输入http://localhost:8848/nacos
-
创建csmall项目
-
添加依赖 <dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
-
添加配置 spring:
application:
name: nacos-business
cloud:
nacos:
discovery:
server-addr: localhost:8848
-
在保证nacos已经启动的前提下,我们启动business项目
心跳机制
-
临时实例(默认) -
持久化实例(永久实例)
临时实例
默认情况下,启动服务后,每隔5秒会向nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息
Nacos收到这个"心跳包"如果发现这个服务的信息不在注册列表中,就进行注册,如果这个服务的信息在注册列表中就表明这个服务还是健康的
如果Nacos15秒内没接收到某个服务的心跳包,Nacos会将这个服务标记为不健康的状态
如果30秒内没有接收到这个服务的心跳包,Nacos会将这个服务从注册列表中剔除
这些时间都是可以通过配置修改的
持久化实例(永久实例)
持久化实例启动时向nacos注册,nacos会对这个实例进行持久化处理
心跳包的规则和临时实例一致,只是不会将该服务从列表中剔除
Dubbo(远程调用)(service)
-
什么是RPC
-
什么是Dubbo
- Dubbo是一套RPC框架。既然是框架,我们可以在框架结构高度,定义Dubbo中使用的通信协议,使用的序列化框架技术,而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。
- 简单来说,Dubbo就是RPC概念的实现
- 能够实现微服务项目的互相调用
-
Dubbo的协议支持
- Dubbo的协议支持
- dubbo协议(默认)
- rmi协议
- hessian协议
- http协议
- webservice
- …
- 支持的序列化协议
- hessian2(默认)
- java序列化
- compactedjava
- nativejava
- fastjson
- dubbo
- fst
- kryo
- Dubbo默认情况下,协议的特征如下
- 采用NIO单一长连接
- 优秀的并发性能,但是大型文件的处理差
- Dubbo开发简单,有助于提升开发效率
-
Dubbo服务的注册与发现
- 在Dubbo的调用过程中,必须包含注册中心的支持(注册中心推荐使用Nacos,但是如果使用其他软件也能实现例如(Redis,zookeeper等))
- 服务发现,即消费端自动发现服务地址列表的能力,是微服务框架需要具备的关键能力,借助于自动化的服务发现,微服务之间可以在无需感知对端部署位置与 IP 地址的情况下实现通信。
- 发现者,它能够获取消费者的所有功能列表
-
服务的提供者,指服务的拥有者---------生产者(provider) -
服务的消费者,指服务的调用者---------消费者(consumer) -
注册中心,在Dubbo中,远程调用依据是服务的提供者在Nacos中注册的服务名称
- 一个服务名称,可能有多个运行的实例,任何一个空闲的实例都可以提供服务
常见面试题:Dubbo的注册发现流程
1.首先服务的提供者启动服务到注册中心注册,包括各种ip端口信息,Dubbo会同时注册该项目提供的远程调用的方法
2.服务的消费者(使用者)注册到注册中心,订阅发现
3.当有新的远程调用方法注册到注册中心时,注册中心会通知服务的消费者有哪些新的方法,如何调用的信息
4.RPC调用,在上面条件满足的情况下,服务的调用者无需知道ip和端口号,只需要服务名称就可以调用到服务提供者的方法
-
负载均衡
- 什么是负载均衡
-
在实际开发中,一个服务基本都是集群模式的,也就是多个功能相同的项目在运行,这样才能承受更高的并发,这时一个请求到这个服务,就需要确定访问哪一个服务器… -
Dubbo框架内部支持负载均衡算法,能够尽可能的让请求在相对空闲的服务器上运行,我们要实现设置好负载均衡的策略算法,并设置好每个服务器的运行权重,才能更好的实现负载均衡的效果. -
Loadbalance:就是负载均衡的意思 -
Dubbo内置负载均衡策略算法(Dubbo内置4种负载均衡算法)
-
random loadbalance:随机分配策略(默认) 1.
- 随机生成随机数,在哪个范围内让哪个服务器运行
- 优点:算法简单,效率高,长时间运行下,任务分配比例准确
- 缺点:偶然性高,如果连续的几个随机请求发送到性能弱的服务器,会导致异常甚至宕机
-
round Robin Loadbalance:权重平均分配
-
如果几个服务器权重一致,那么就是依次运行,但是服务器的性能权重一致的可能性很小,所以我们需要权重评价分配 -
如果3个服务器的权重比5:3:1 1>1 2>1 3>1 4>1 5>1 6>2 7>2 8>2 9>3 10>1 -
Dubbo2.7之后更新了这个算法使用"平滑加权算法"优化权重平均分配策略 -
leastactive Loadbalance:活跃度自动感知分配
- 记录每个服务器处理一次请求的时间,安装时间比例来分配任务数,运行一次需要时间多的分配的请求数较少
-
consistanthash Loadbalance:一致性hash算法分配
- 根据请求的参数进行hash运算,以后每次相同参数的请求都会访问固定服务器,因为根据参数选择服务器,不能平均分配到每台服务器上,使用的也不多
-
Dubbo实现微服务调用
- 确定调用关系
- 生产者
- 消费者
- 既是生产者又是消费者
Dubbo生产者消费者相同的配置
-
pom文件添加dubbo依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
-
yml文件配置dubbo信息 dubbo:
protocol:
port: -1
name: dubbo
registry:
address: nacos://localhost:8848
consumer:
check: false
Dubbo生产者消费者不同的配置
- 生产者
- 要有service接口项目
- 提供服务的业务逻辑层(service)实现类要添加**@DubboService**注解
- SpringBoot启动类要添加**@EnableDubbo**注解
- 消费者
- pom文件添加消费模块的service依赖
- 业务逻辑层(service)远程调用模块时使用**@DubboReference**注解获取业务逻辑层实现类对象
Seata(分布式事务)(service)
-
下载Seata
- https://github.com/seata/seata/releases
- https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip
-
什么是Seata
- Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
- Seata官方文档https://seata.io/zh-cn/
-
为什么需要Seata
-
事务的4个特性:ACID特性
-
我们再业务中,必须保证数据库操作的原子性,也就是当前业务的所有数据库操作要么都成功,要么都失败.之前我们使用Spring声明式事务来解决本地的事务问题,但是现在是微服务环境,一个业务可能涉及多个模块的数据库操作,这种情况就需要专门的微服务状态下解决事务问题的"分布式事务"解决方案. -
Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案 -
Seata的运行原理(AT模式) ?
-
上面结构是比较典型的远程调用结构 -
如果account操作数据库失败需要让order模块和storage模块撤销(回滚)操作 -
声明式事务不能完成这个操作,需要Seata来解决,解决模型如下 -
Seata构成部分包含
- 事务协调器TC
- 事务管理器TM
- 资源管理器RM
-
AT(自动)模式完成分布式事务的解决
- AT模式运行过程
- 事务的发起方?会向事务协调器(TC)申请一个全局事务id,并保存
- Seata会管理事务中所有相关的参与方的数据源,将数据操作之前和之后的镜像都保存在undo_log表中,这个表是seata框架规定的,方便提交(commit)或回滚(roll back)
- 事务的发起方?会连同全局id一起通过远程调用运行资源管理器(RM)中的方法
- 资源管理器(RM)接收到全局id,并运行指定的方法,将运行的状态同步到事务协调器(TC)
- 如果运行整体没有发生异常,发起方?会通过事务协调器通知所有分支,将本次事务所有对数据库的影响真正生效,反之如果任何一个RM运行发生异常,那么都会通知事务协调器,再由事务协调器通知所有分支,回滚数据中的数据
- 回滚时可以使用undo_log表中的数据来实现回滚
- 其他模式简介
- AT模式运行有一个非常明显的条件,就是事务分支都必须是操作关系型数据库(mysql\MariaDB\Oracle),但是如果一个事务中有操作例如Redis这样的非关系型数据库时就不能使用AT模式了
- TCC模式
- 这个模式简单来说就是自己编写代码进行事务的提交和回滚
- 我们需要在各个分支业务逻辑层代码中编写一组三个方法(prepare\commit\rollback)
- prepare:准备 commit:提交 rollback:回滚
- prepare方法是无论事务成功与否都会运行的代码
- commit当整体事务运行成功时运行的方法
- rollback当整体事务运行失败是运行的方法
- 优点:虽然代码是自己写的,但是事务整体提交或回滚的机制仍然可用
- 缺点:每个业务都要编写3个方法来对应,代码冗余,而且业务入侵量大
- SAGA模式
- SAGA模式的思想是编写一个类,当指定的事务发生问题时,运行SAGA编写的回滚类
- 这样编写代码不影响已经编写好的业务逻辑代码,一般用于修改已经编写完成的老代码,缺点是每个事务分支都要编写一个类来回滚业务,类数量多,开发量大
- XA模式
- 支持XA协议的数据库分布式事务,使用比较少
-
使用Seata
-
配置Seata(生产者)
-
添加依赖()
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
-
配置文件yml() seata:
tx-service-group: csmall_group
service:
vgroup-mapping:
csmall_group: default
grouplist:
default: localhost:8091
注意同一个事务必须在同一个tx-service-group中,同时指定相同的seata地址和端口 -
消费者
-
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
-
seata:
tx-service-group: csmall_group
service:
vgroup-mapping:
csmall_group: default
grouplist:
default: localhost:8091
-
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(门卫,哨兵)(controller)
-
什么是Sentinel
- Sentinel也是Spring Cloud Alibaba的组件
- 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
- 官网地址https://sentinelguard.io/zh-cn/
- 下载地址https://github.com/alibaba/Sentinel/releases
-
为什么需要Sentinel
- 丰富的应用场景--------双11,秒杀,12306抢火车票
- 完备的实时状态监控
- 可以支持显示当前项目各个服务的运行和压力状态,分析出每台服务器处理的秒级别的数据
- 广泛的开源生态
- 很多技术可以和Sentinel进行整合,SpringCloud,Dubbo,而且依赖少配置简单
- 完善的SPI扩展
- Sentinel支持程序设置各种自定义的规则
-
基本配置和限流效果
-
添加pom依赖 <dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
-
修改yml配置 spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8721
-
Sentinel限流针对控制层方法@SentinelResource(“减少库存方法(控制器)”) @PostMapping("/reduce/count")
@ApiOperation("减少商品库存业务")
@SentinelResource("减少库存方法(控制器)")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
-
流控与降级 @PostMapping("/reduce/count")
@ApiOperation("减少商品库存业务")
@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("商品库存减少完成!");
}
public JsonResult fallbackError(StockReduceCountDTO stockReduceCountDTO){
return JsonResult.failed(ResponseCode.BAD_REQUEST,"因为运行异常,服务降级");
}
blockHandler和fallback的区别
两者都是不能正常调用资源返回值的顶替处理逻辑.
blockHander只能处理BlockException 流控限制之后的逻辑.
fallback处理的是资源调用异常的降级逻辑.
SpringGateway 网关
早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎
这些框架和Spring Cloud Alibaba的对应关系我们要知道
Nacos对应Eureka 都是注册中心
Dubbo对应ribbon+feign都是实现微服务间调用
Sentinel对应Hystrix都是项目限流熔断降级组件
Gateway对应zuul都是项目的网关
Gateway不是阿里巴巴的而是Spring提供的
-
什么是网关
-
“网关"网是网络,关是关口\关卡,关口\关卡的意思就是"统一入口”,网关:就是网络中的统一入口, -
程序中的网关就是微服务项目提供的外界所有请求统一访问的微服务项目 -
因为提供了统一入口之后,方便对所有请求进行统一的检查和管理
-
网关的主要功能有
- 将所有请求统一由经过网关
- 网关可以对这些请求进行检查
- 网关方便记录所有请求的日志
- 网关可以统一将所有请求路由到正确的模块\服务上
-
Spring Gateway简介
- Spring Gateway是Spring自己编写的,也是SpringCloud中的组件
- SpringGateway官网https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
-
简单网关演示
-
网关是一个我们创建的项目,不是一个需要安装的软件,网关也是当前微服务项目的一员,也要注册到Nacos,所以保证Nacos的运行,运行之前,我们看一下网关演示项目已经存在的基本结构,beijing和shanghai是编写好的两个项目,gateway没有编写yml文件配置,要想实现网关的路由效果需要修改yml文件如下 server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- id: gateway-beijing
uri: lb://beijing
predicates:
- Path=/bj/**
-
内置断言
-
断言就是判断一个条件,如果条件满足就执行某个操作 -
predicates就是断言的意思 -
Path就是内置断言中的一种,指访问的路径是否满足条件 -
除了路径断言之外,还有很多内置断言常见的内置断言列表
- after
- before
- between
- cookie
- header
- host
- method
- path
- query
- remoteaddr
-
时间相关
-
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:
enabled: true
-
创建gateway网关子项目
-
子项目pom文件为 <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<!-- Spring Gateway 网关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--聚合网关 knife4j-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
-
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 -> {
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);
};
}
}
|