目录
一.下载与使用Nacos(spring cloud 注册中心)
2.配置nacos 默认端口号8848 (http://localhost:8848/nacos)可以访问到nacos
微服务框架的五大组件(阿里巴巴)
Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
一.下载与使用Nacos(spring cloud 注册中心)
1.nacos下载地址
https://github.com/alibaba/nacos/releases/download/1.4.3/nacos-server-1.4.3.zip
下载解压到本地文件夹即可
启动nacos服务:
启动Nacos不能直接双击startup.cmd
而需要打开dos窗口来执行
在安装路径下nacos的ben文件夹下,输入下面的代码启动
startup.cmd -m standalone
-m standalone 是单体使用,没有配置集群需要加上去
{在idea中启动nacos 需要的配置}
2.配置nacos 默认端口号8848 (http://localhost:8848/nacos)可以访问到nacos
(1)在pom文件中添加nacos配置
(2)在yml文件中添加配置
以上配置都是将nacos配置到各服务中
二、Dubbo的配置学习
1.服务之间的相互调用
Dubbo 是一套RPC框架 默认的通信协议是Dubbo自己写的协议,序列化协议使用的就是json
/**通信协议和序列化协议是可以通过配置文件修改的*/
注:RPC是Remote Procedure Call 翻译为:远程过程调用
RPC主要包含了2个部分的内容
- 序列化协议
- 通信协议
通信协议 当老婆在外面时,就需要借助通讯工具通知老公来完成什么操作 在这个流程中通信修协议就指老婆使用什么方式通知老公要洗碗 可以是手机,也可以写信,可以飞鸽传书 序列化协议 指传输信息的内容是什么格式,双方都要能够理解这个格式 例如老婆说中文,老公要理解中文,其他语言也一样 发送信息是序列化的过程,接收信息是反序列化的过程 这样他们才能明确调用的目的 这样的流程就是我们生活中的RPC使用的场景 |
因为 Dubbo内置了序列化协议和通信协议,所以会有一下特性:
采用NIO单一长连接
优秀的高并发处理性能
编写简单,提升开发效率
配置:
服务之间的相互调用 ,是分为服务的提供者和消费者
1. 提供者项目需要,将原项目中的service接口分离出来,形成一个新的子项目,父子相认(非必要,但是这是行业规范)
2. 将原项目中的实现代码也形成一个新的子项目(webapi),父子相认,在pom文件中添加dubbo依赖
3.将原项目的application.yml复制到webapi项目中,然后在application.yml中添加dubbo配置
4.需要在webapi子项目的service层中添加@DubboService注解,该注解的作用与@service相同
5.需要在webapi子项目的启动类中添加@EnableDubbo注解
6.还有就是业务逻辑层的实现有修改
之前order模块只是单纯的添加订单信息到数据库
现在我们要在添加订单到数据库之前先删除库存和购物车中的信息
@DubboReference 注解的作用与@Autowired注解作用相同
消费者项目,
1.只需要在pom文件中添加依赖
2.在application.yml文件中添加配置(依照上述即可)
3. 在消费者模块的业务逻辑层调用提供者的业务的方法(仅消费是不用添加@DubboService注解的)
例:
4.最后需要在启动类添加@EnableDubbo注解
(服务的调用dubbo完)
三. 分布式事务
不是一个项目的数据库操作,所以不能简单的通过一个数据库事务就完成
每个项目都有自己的数据库事务,那么要想在多个事务的提交过程中再添加支持他们事务就需要Seata
Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata构成
- 事务协调器TC
- 事务管理器TM
- 资源管理器RM
AT模式运行过程
1.事务的发起方(TM)会向事务协调器(TC)申请一个全局事务id,并保存
2.Seata会管理事务中所有相关的参与方的数据源,将数据操作之前和之后的镜像都保存在undo_log表中,这个表是seata框架规定的,方便提交(commit)或回滚(roll back)
3.事务的发起方(TM)会连同全局id一起通过远程调用运行资源管理器(RM)中的方法
4.资源管理器(RM)接收到全局id,并运行指定的方法,将运行的状态同步到事务协调器(TC)
5.如果运行整体没有发生异常,发起方(TM)会通过事务协调器通知所有分支,将本次事务所有对数据库的影响真正生效,
如果任何一个参与者发生异常,那么都会通知事务协调器,再由事务协调器通知有分支,根据undo_log表中保存的信息,撤销(回滚)即将正式影响数据库的数据
除AT模式外的其他模式简介
Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式
AT模式只能用于数据库操作事务
如果事务中有的参与者操作的不是关系型数据库(例如操作Redis)
那么AT模式就不能生效了
TCC模式
这个模式可以实现对数据库之外的信息存储媒介进行回滚操作
只不过这个回滚需要我们自己编写代码
需要为每个业务编写Prepare\Commit\Rollback方法
Prepare编写常规准备,如果整个业务运行无异常运行Commit,如果有异常会自动运行Rollback
缺点是每个业务都需要编写3个对应的方法,代码有冗余,而且业务入侵量大
SAGA模式
一般用于修改老版本代码
不用编写像TCC模式那么多的方法
但是需要手动编写每个参与者的方向回滚的业务逻辑层代码类
开发量大
XA 模式
XA是适用于支持XA协议的数据库,使用的比较少
下载Seata地址
https://github.com/seata/seata/releases
https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip
配置 seata:
1.在参与者模块的pom文件中添加seata的依赖
<!-- Seata和SpringBoot整合依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!-- Seata 完成分布式事务的两个相关依赖(Seata会自动使用其中的资源) -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
2. 修改参与者模块yml文件
seata:
tx-service-group: csmall_group # 分组
service:
vgroup-mapping:
csmall_group: default # 默认at模式
grouplist:
default: localhost:8091
配置中,同一个事务的多个参与者必须在同一个名称的分组下
同时制定相同的seata-server的ip和端口
3.在发起者模块的pom文件中添加依赖
<!-- Seata和SpringBoot整合依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
在发起者模块的pom文件中只需要添加一个依赖
4.application-dev.yaml和之前的一样
seata:
tx-service-group: csmall_group # 分组
service:
vgroup-mapping:
csmall_group: default # 默认at模式
grouplist:
default: localhost:8091
5.消费者模块加入注解
发起者组织事务开始
必须由特定的注解标记业务逻辑层方法(@GlobalTrancsational)
在这个方法运行时会激活Seata的分布式事务管理流程
4.启动seata服务
进入seata\seata-server-1.4.2\bin目录
打开cmd窗口
输入seata-server.bat -h 127.0.0.1 -m file 命令
在windows系统中运行seata可能出现不稳定的情况,重启seata即可解决
四.Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
下载地址
https://github.com/alibaba/Sentinel/releases
Sentinel特征
-
丰富的应用场景
双11,秒杀,12306抢火车票
-
完备的实时状态监控
可以支持显示当前项目各个服务的运行和压力状态,分析出每台服务器处理的秒级别的数据
-
广泛的开源生态
很多技术可以和Sentinel进行整合,SpringCloud,Dubbo,而且依赖少配置简单
-
完善的SPI扩展
Sentinel支持程序设置各种自定义的规则
基本配置和限流效果
限流:
1.在pom文件中添加依赖
<!-- Sentinel整合SpringCloud -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.在application-dev.yml配置Sentinel支持
spring:
application:
name: nacos-stock # 定义当前服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 指定正在运行的Nacos服务器的位置
sentinel:
transport:
dashboard: localhost:8080 # Sentinel仪表台的ip:端口
port: 8721 # 是localhost的8721 这个端口真正参与当前项目的限流措施
注意:每个项目模块的 port:8721 端口号都不能相同
3.下面要使用注解标记限流的控制层方法
运行控制器方法之后
Sentinel的仪表台就能设置这个控制方法的限流规则了
QPS是每秒请求数
并发线程数是同时方法这个方法的线程数量
超出的部分都会被Sentinel限流,快速失败
此外,sentinel还支持降级
@RestController
@RequestMapping("/base/business")
@Api(tags = "购买业务开始模块")
public class BusinessController {
@Autowired
private IBusinessService businessService;
@PostMapping("/buy") // localhost:20000/base/business/buy
@ApiOperation("发起购买")
// 标记当前控制层方法被Sentinel管理限流
// blockHandler配置当请求被限流时 运行哪个方法
// fallback配置当控制器方法运行异常时,运行哪个方法
@SentinelResource(value = "buy",
blockHandler = "blockError",fallback = "fallbackError")
public JsonResult buy(){
// 调用业务逻辑层方法,业务逻辑层方法没有返回值
businessService.buy();
return JsonResult.ok("购买完成");
}
// Sentinel规定的限流相应的方法有如下要求
// 1.必须是public的方法
// 2.返回值类型必须和控制器方法一致
// 3.参数必须在控制方法的一样的前提下,在参数列表最后添加一个BlockException
// 4.方法名必须和上面blockHandler指定的一致
public JsonResult blockError(BlockException e){
return JsonResult.failed(ResponseCode.BAD_REQUEST,"服务器忙!");
}
// 当控制器方法运行发送异常时,Sentinel支持服务降级
// 我们可以在这个方法中对用户的请求进行一些补救
// 方法格式和上面限流方法一致,只是不必须添加BlockException参数
public JsonResult fallbackError(){
return JsonResult.failed(ResponseCode.BAD_REQUEST,"服务降级");
}
}
blockHandler
- 属性值是流控后调用的方法名称
- 流控处理方法返回值和资源方法返回值一致
- 流控处理方法参数和资源方法一致的同时,可以接收额外参数BlockException
- 流控处理方法一定要在资源方法同一个类内,如果不在同一个类,使用blockHandlerClass属性
fallback
- 属性值是降级处理方法名称
- 降级处理方法返回值和资源方法返回值一致
- 降级处理方法参数和资源方法一致的同时,可以接收额外参数BlockException
- 降级处理方法一定要在资源方法同一个类内,如果不在同一个类,使用fallbackClass属性
blockHandler和fallback的区别
两者都是不能正常调用资源返回值的顶替处理逻辑.
blockHander只能处理BlockException 流控限制之后的逻辑.
fallback处理的是资源调用异常的降级逻辑.
五.Spring Gateway 网关
所谓网关,就是请求到当前微服务项目,当前项目为访问者提供的统一入口
网关可以将多个项目模块微服务程序混乱的调用关系变得简单
我们使用SpringGateway作为当前项目的网关组件
Spring Gateway是Spring自己开发的,也是SpringCloud中的组件之一
gateway的yml配置
server: port: 9000 spring: application: name: gateway cloud: nacos: discovery: server-addr: localhost:8848 gateway: routes: # gateway配置路由信息开始 # spring.cloud.gateway.routes[0].predicates[0] - id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - id: gateway-beijing # 设置路由的id,和其他任何名称没有关联,只是不能和其他路由id重名即可 # uri表示设置路由的目标 # lb是LoadBalance(负载均衡)的缩写 beijing是注册到nacos的服务名称 uri: lb://beijing # predicates是断言 所谓断言就是判断一个条件,如果满足的话就做某些指定的操作 predicates: # Path表示判断是否满足路径条件,如果访问当前网关项目的路径是/bj/**, # name本次访问网关的路由目标就是uri指定的路径 - Path=/bj/** # http://localhost:9001/bj/show
可以动态路由,以上配置是底层,参考即可
内置断言
所谓断言就是判断一个条件,如果满足的话就做某些指定的操作
predicates的意思就是断言
我们前面章节中使用的Path是内置断言的一种,指定满足某些路径特征时访问某些资源
predicates参数也是支持数组的,Path只是数组的一个元素而已,我们可以添加更多其他含义的内置断言
内置断言就是SpringGateway框架提供的断言规则,他们有
- after
- before
- between
- cookie
- header
- host
- method
- path
- query
- remoteaddr
时间相关
after,before,between
在指定时间之前,之后,之间访问,满足这个时间条件能够访问指定资源
为了方便获得一个时间格式我们可以使用这个方法
ZonedDateTime.now()// 获得系统当前时间
判断时间必须在xxx之后 After
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - After=2022-05-07T15:32:03.138+08:00[Asia/Shanghai]
判断时间必须在xxx之前 Before
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - Before=2022-05-07T15:32:03.138+08:00[Asia/Shanghai]
判断时间必须在xxx与xxx之间 Between
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - Between=2022-05-07T15:32:03.138+08:00[Asia/Shanghai],2022-05-07T15:42:03.138+08:00[Asia/Shanghai]
要求指定参数
Query断言,要求必须有指定参数才能访问指定资源
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** - Query=name
Gateway过滤器
SpringGateway除了断言选项之外还提供了网关专业过滤器
在请求经过网关是,对请求进行一些操作
filters是添加过滤器的属性,也是支持数组赋值的
我们下面尝试一个将请求中添加参数信息的过滤器
- id: gateway-shanghai uri: lb://shanghai predicates: - Path=/sh/** filters: - AddRequestParameter=age,18
在shanghai项目的Controller中获得这个参数
@GetMapping("/show") public String show(Integer age){ return "这里是上海!"+age; }
动态路由
如果一个微服务项目有很多模块
每个模块都手动配置路由规则的话,随着模块的增加,工作量也会越来越大
今后对这个路由配置文件的维护会非常的麻烦
我们希望SpringGateway能够自动的,动态的从Nacos中获得当前注册到微服务项目的模块
无需配置文件,给定固定格式就可以路由到这个模块
这就是动态路由配置
它的配置非常简单
server: port: 9000 spring: application: name: gateway cloud: nacos: discovery: server-addr: localhost:8848 gateway: discovery: locator: # 开启动态路由,网关会自动从Nacos注册列表中寻找指定的服务器名称,路由到该项目 enabled: true
创建gateway网关项目
先父子相认
删除test文件夹
pom文件需要的依赖
<dependencies> <!-- web实例 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Nacos注册依赖 --> <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-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> </dependency>
yml需要的配置
server: port: 10000 spring: application: name: gateway-server cloud: nacos: discovery: server-addr: localhost:8848 //指定nacos服务器的地址 gateway: discovery: locator: # 网关开启动态路由 enabled: true main: web-application-type: reactive //解决SpringMvc和SpringGateway冲突问题
cn.tedu.gateway.config
SwaggerProvider类
@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;
}
}
cn.tedu.gateway.controller
SwaggerController类
@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)));
}
}
cn.tedu.gateway.filter
SwaggerHeaderFilter类
@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);
};
}
}
网关项目创建完毕
启动所有服务
应该最后启动网关项目
SpringMvc和SpringGateway冲突问题
<!-- web实例 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
和
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
依赖会起冲突
原因是SpringMvc依赖自带了Tomcat服务器
而SpringGateway依赖自带了Netty服务器
当项目启动时,会两个服务器软件就冲突了
导致错误
我们可以在yml文件中添加下面配置解决
spring: main: web-application-type: reactive
至此五大组件介绍完毕
奈非框架简介
早期奈非公司提供的框架受到了很多微服务开发者的欢迎
它提供的组件几乎包含了所有微服务架构搭建的需要
eureka注册中心(Nacos)
ribbon+feign微服务间调用(Dubbo)
hystrix流控(Sentinel)
zuul网关(Gateway)
近几年奈非公司项目更新慢,跟不上需求,所以很多开发结构转向阿里
(同类的微服务框架,眼熟即可)