目录
前言
系统架构演变
1、单体架构
2、垂直架构
3、分布式架构
4、SOA架构
5、微服务架构
一、微服务架构的介绍
1、微服务架构的常见问题
2 微服务架构的常见概念
2.1 服务治理
2.2 服务调用
2.3 服务网关
2.4 服务容错
2.5 链路追踪
3、微服务架构的常用解决方案
3.1 ServiceComb
3.2 SpringCloud
3.3 SpringCloud Alibaba
二、微服务环境的搭建
1.技术选型
2、模块设计
3、项目初始化
三、Nacos Discovery --服务治理
1、服务治理介绍
2、 nacos的介绍
3、 nacos实战
3.1 搭建nacos环境
3.2 将各个微服务注册到nacos中
四、基于Feign实现服务调用
1、什么是Feign
2、为什么要使用Feign
1.1 使用RestTemplate进行访问
1.2 引入nacos使用DiscoveryClient的getInstances方法
1.3 使用Ribbot负载均衡和RestTemplate
3、Feign的使用
3.1 加入Feign依赖
3.2 在注解中开启Feign
3.3 创建service进行调用远程服务
3.4 订单调用商品远程服务
3.5 重启order服务,查看效果
五、Sentinel 服务容错
1.高并发带来的问题
1.1 编写Java代码
1.2 修改配置文件中tomcat的并发数
1.3 利用压测工具Jemeter
Apache JMeter - Apache JMeter™
1.4 测试
2. 服务雪崩效应
3.常见的容错方案
3.1 隔离
3.2 超时
3.3 限流
3.4 熔断
3.5 降级
4.常见的容错组件
5、什么是Sentinel
5.1 快速入门
5.2 使用Sentinel控制台
5.3 实现一个接口的限流
5.4 Sentinel的概念和功能
5.5 重要规则
6、Sentinel的规则
1.流控规则
1.1 简单配置:
1.2 高级配置 流控模式
1.3 高级配置 流控效果
2.熔断规则
2.1 平均响应时间 :
2.2 异常比例:
编辑
2.3 异常数 :
3. 热点规则
3.1 热点规则简单使用
3.2 热点规则增强使用
4.授权规则
5.系统规则
6.自定义异常返回
7.@SentinelResource的使用
8.Feign整合Sentinel
前言
系统架构演变
1、单体架构
单体架构:小型网站
一般的网站应用流量较小,只需一个应用,将所有功能代码都部署在一起就可以,这
样可以减少开发、部署和维护的成本。
2、垂直架构
垂直架构,将应用分开解决了无法水平扩展的问题
随着访问量的逐渐增大,单一应用只能依靠增加节点来应对,但是这时候会发现并不是所有的模块
都会有比较大的访问量.
以电商为例子, 用户访问量的增加可能影响的只是用户和订单模块, 但是对消息模块的影响就比较小. 那么此时我们希望只多增加几个订单模块, 而不增加消息模块. 此时单体应用就做不
到了, 垂直应用就应运而生了.
3、分布式架构
分布式建构:将一些共同需要调用的模块提取出来,解决代码重复问题
当垂直应用越来越多,重复的业务代码就会越来越多。这时候,我们就思考可不可以将重复的代码
抽取出来,做成统一的业务层作为独立的服务,然后由前端控制层调用不同的业务层服务呢?
这就产生了新的分布式系统架构。它将把工程拆分成表现层和服务层两个部分,服务层中包含业务
逻辑。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。
4、SOA架构
SOA架构:设置一个服务治理中心帮我们管理服务之间的调用关系
在分布式架构下,当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加
一个调度中心对集群进行实时管理
5、微服务架构
微服务架构在某种程度上是面向服务的架构SOA继续发展的下一步,它更加强调服务的"彻底拆分"。
一、微服务架构的介绍
微服务架构简单的来说,就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目(一般按照业务进行拆分)
1、微服务架构的常见问题
一旦采用微服务系统架构,就势必会遇到这样的几个问题:
- 这么多小的服务,如何管理他们? (服务治理-->注册中心【服务注册 服务发现 服务剔除】 nacos 、eureka等)
- 这么多小的服务,他们之前是如何通讯? (restful (Feign)、rpc(Dubbo))
- 这么多小的服务,客户端如何访问他们(网关)
- 这么多的小的服务,一旦出现问题,应该如何自处理,防止出现雪崩问题(容错处理)
- 这么多小的服务,一旦出现问题,应该如何排错(链路追踪,进行记录日志)
对应上面的问题,是任何一个微服务使用不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。
2 微服务架构的常见概念
2.1 服务治理
服务治理就是进行服务的自动化管理,其核心是服务的自动注册与发现。
服务注册:服务实例将自身服务信息注册到注册中心。
服务发现:服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。
服务剔除:服务注册中心将出问题的服务自动剔除到可用列表之外,使其不会被调用到。
2.2 服务调用
在微服务架构中,通常存在多个服务之间的远程调用的需求。目前主流的远程调用技术有基于
HTTP的RESTful接口以及基于TCP的RPC协议。
REST(Representational State Transfer)
这是一种HTTP调用的格式,更标准,更通用,无论哪种语言都支持http协议
RPC(Remote Promote Call)
一种进程间通信方式。允许像调用本地服务一样调用远程服务。 RPC框架的主要目标就是让远程服务调用更简单、透明。 RPC框架负责屏蔽底层的传输方式、序列化方式和通信细节。开发人员在使用的时候只需要了解谁在什么位置提供了什么样的远程服务接口即可,并不需要关心底层通信细节和调用过程。
2.3 服务网关
随着微服务的不断增多,不同的微服务一般会用不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务的需求,如果让客户端直接与各个微服务通信可能出现:
- 客户端需要调用不同的url地址,增加难度
- 在一定的场景下,存在跨区请求的问题
- 每个微服务都需要进行单独的身份认证
针对这样的问题,API网关顺势而生。
API网关:将所有API调用统一接入API网关层,由网关层统一接入和输出,一个网关的基本功能有:统一接入、安全防护、协议适配、流量管控、长短链接支持、容错能力。有了网关之后,各个API服务提供团队可以专注于自己的业务逻辑,而API网关更专注于安全、流量、路由等问题
2.4 服务容错
在微服务中,一个请求经常会涉及到调用几个服务,如果其中某个服务不可用,没有服务容错的话,既有可能造成一连串的服务不可用,这就是雪崩效应。
我们无法预防雪崩效应的发生,只能尽可能去做好容错。服务容错的三个核心思想是:
- 不被外界环境影响
- 不被上游请求压垮 (流控)
- 不被下游响应拖垮 (熔断)
2.5 链路追踪
随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。互联
网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要对一次请求涉及的多个服务链路进行日志记录,性能监控即链路追踪
3、微服务架构的常用解决方案
3.1 ServiceComb
Apache ServiceComb,前身是华为云的微服务引擎 CSE (Cloud Service Engine) 云服务,是全球
首个Apache微服务顶级项目。它提供了一站式的微服务开源解决方案,致力于帮助企业、用户和开发者将企业应用轻松微服务化上云,并实现对微服务应用的高效运维管理。
3.2 SpringCloud
Spring Cloud是一系列框架的集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基
础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服
务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
3.3 SpringCloud Alibaba
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服
务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务
为什么这里使用SpringCloud Alibaba ?
SpringCloud 使用的很多组件宣布已经停止维护了或者闭源了,因此使用SpringCloud Alibaba,它的性能更好,是经过了淘宝双十一的测试过的。
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服
务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
主要功能
- 服务限流降级:默认支持 WebServlet、 WebFlux, OpenFeign、 RestTemplate、 Spring Cloud、Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
- 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
- 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。 RocketMQ
- 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有Worker(schedulerx-client)上执行。
- 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
组件
- Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
- Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
- RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
- Dubbo: Apache Dubbo™ 是一款高性能 Java RPC 框架。
- Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。Alibaba Cloud ACM:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。
- Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
- Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道
二、微服务环境的搭建
1.技术选型
maven:3.6.0
数据库:MySQL 5.7
持久层 Mybaits plus
其他:SpringCloud Alibaba 技术栈
2、模块设计
springcloud-alibaba 父工程
common 公共模块common-utils :全局异常处理类、自定义异常、统一返回类、返回的code编码
service-utils:分页配置、swagger接口文档的配置、Request请求的工具类
model 实体类模块:实体类、返回vo、请求req、枚举类
service 模块
order-service 订单模块 8091
product-service 商品模块 8071
user-service 用户模块 8081
3、项目初始化
项目gitee地址:SpringCloud Alibaba : SpringCloud Alibaba 技术栈的代码
三、Nacos Discovery --服务治理
1、服务治理介绍
服务治理是微服务架构中最核心最基本的模块。用于解决业务增多时,各服务之间的调用问题。用于实现各个微服务的自动化注册与发现
服务注册:在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息(ip和端口)。并在注册中心形成一张服务的清单,服务注册中心需要以心跳的方式去监测清单中的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
服务发现: 服务调用放(消费者)向服务注册中心咨询服务,并获取所有服务的实例清单,实现对具体服务实例的访问。
通过上面的调用图会发现,除了微服务,还有一个组件是服务注册中心,它是微服务架构非常重要的一个组件,在微服务架构例主要起到了协调组的一个作用。注册中心一般包含如下几个功能:
1、服务发现:
服务注册:保存服务提供者和服务调用者的信息
服务订阅:服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息
2、服务配置:
配置订阅:服务提供者和服务调用者订阅微服务相关的配置
配置下发:主动将配置推送给服务提供者和服务调用者
3、服务健康检测
检测服务提供者的健康情况,如果发现异常,执行服务剔除
常见的注册中心
Zookeeper
zookeeper是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式
应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用
配置项的管理等。
Eureka
Eureka是Springcloud Netflix中的重要组件,主要作用就是做服务注册和发现。但是现在已经闭
源
Consul
Consul是基于GO语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册、服务发现
和配置管理的功能。 Consul的功能都很实用,其中包括:服务注册/发现、健康检查、 Key/Value
存储、多数据中心和分布式一致性保证等特性。 Consul本身只是一个二进制的可执行文件,所以
安装和部署都非常简单,只需要从官网下载后,在执行对应的启动脚本即可。
Nacos
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 Spring
Cloud Alibaba 组件之一,负责服务注册发现和服务配置,可以这样认为nacos=eureka+config。
2、 nacos的介绍
Nacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
从上面的介绍就可以看出,nacos的作用就是一个注册中心,用来管理注册上来的各种微服务
3、 nacos实战
3.1 搭建nacos环境
地址:Releases · alibaba/nacos · GitHub
-
下载解压后,在bin目录下的,进入黑窗口
-
然后使用单机版启动nacos ,window命令:
staratup.cmd -m standalone
- 访问即可 http://localhost:8848/nacos/index.html 即可
默认账号密码nacos
3.2 将各个微服务注册到nacos中
将shop-service注册到nacos注册中心中
①引入pom依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2021.0.1.0</version>
</dependency>
②在主类中加入@EnableDiscoveryClient注解
@SpringBootApplication
@MapperScan("com.sofwin.mapper")
@EnableDiscoveryClient
public class OrderApplication
③配置yml配置项
spring:cloud:nacos:discovery:server-addr: localhost:8848
④启动服务, 观察nacos的控制面板中是否有注册上来的订单微服务
四、基于Feign实现服务调用
1、什么是Feign
Feign是SpringCloud提供的一个声明式的伪Http客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了Feign,Feign默认继承了Ribbon,所以在Nacos下使用Feign默认就实现了负载均衡的效果。
2、为什么要使用Feign
情况:
当创建订单的时候需要先查询商品从才能创建订单
没有Fiegn的时候服务间的调用过程:
订单需要调用商品信息的代码:
@RestController
@Api("商品模块")
@Slf4j
public class PorductController {@Autowiredprivate ShopProductService shopProductService;@ApiOperation("通过pid查询商品信息 ")@GetMapping("/product/{pid}")public BaseResult<ShopProduct> getPorductById(@PathVariable("pid") Long pid) {log.info("接下来要进行{}号商品信息的查询",pid);ShopProduct shopProduct = shopProductService.getById(pid);log.info("商品信息查询成功,内容为{}", JSON.toJSONString(shopProduct));return BaseResult.success(shopProduct);}}
1.1 使用RestTemplate进行访问
① 在订单启动类上创建RestTemplate对象
@SpringBootApplication
@MapperScan("com.sofwin.mapper")
@EnableDiscoveryClient
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class,args);}@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}
② 在控制层调用商品信息,创建订单
@RestController
@Api("订单模块")
@Slf4j
public class OrderController {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate ShopOrderService shopOrderService;/*** 使用RestTemplate进行调用* 缺点:1、代码写死,硬编码问题 2、器群无法负载调用 3、使用服务增多不容易管理* @param pid* @return*/@ApiOperation("创建订单--使用RestTemplate ")@GetMapping("/order/prod/{pid}")public BaseResult createOrder(@PathVariable("pid") Long pid) {log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息",pid);//调用商品微服务,查询商品信息BaseResult baseResult = restTemplate.getForObject("http://localhost:8071/api/productService/product/"+pid, BaseResult.class);HashMap data = (HashMap) baseResult.getData();String mapToJson = JSON.toJSONString(data);ShopProduct shopProduct = JSON.parseObject(mapToJson, ShopProduct.class);log.info("查询到{}号商品的信息,内容是{}",pid, JSON.toJSONString(shopProduct));//创建订单ShopOrder shopOrder = new ShopOrder();shopOrder.setUid(1L);shopOrder.setUsername("admin");shopOrder.setPid(pid);shopOrder.setPname(shopProduct.getPname());shopOrder.setPprice(shopProduct.getPprice());shopOrder.setNumber(1L);//创建订单boolean save = shopOrderService.save(shopOrder);if (save) {log.info("保存订单成功,订单信息为{}",JSON.toJSONString(shopOrder));return BaseResult.success(shopOrder);}else {return BaseResult.fail("插入失败");}}}
主要问题:
1.代码写死,每次改变地址都需要进行修改,硬编码问题
2. 无法进行器群进行负载均衡
3.服务增多url过多不好管理
1.2 引入nacos使用DiscoveryClient的getInstances方法
@Autowiredprivate DiscoveryClient discoveryClient;/*** 使用discoveryClient RestTemplate进行调用* 解决硬编码问题* @param pid* @return*/@ApiOperation("创建订单--使用discoveryClient和RestTemplate ")@GetMapping("/order/prod2/{pid}")public BaseResult createOrder2(@PathVariable("pid") Long pid) {log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息",pid);//集合是因为器群List<ServiceInstance> instances = discoveryClient.getInstances("product-service");ServiceInstance serviceInstance = instances.get(0);String host = serviceInstance.getHost();int port = serviceInstance.getPort();//调用商品微服务,查询商品信息BaseResult baseResult = restTemplate.getForObject("http://"+host+":"+port+"/api/productService/product/"+pid, BaseResult.class);HashMap data = (HashMap) baseResult.getData();String mapToJson = JSON.toJSONString(data);ShopProduct shopProduct = JSON.parseObject(mapToJson, ShopProduct.class);log.info("查询到{}号商品的信息,内容是{}",pid, JSON.toJSONString(shopProduct));//创建订单ShopOrder shopOrder = new ShopOrder();shopOrder.setUid(1L);shopOrder.setUsername("admin");shopOrder.setPid(pid);shopOrder.setPname(shopProduct.getPname());shopOrder.setPprice(shopProduct.getPprice());shopOrder.setNumber(1L);//创建订单boolean save = shopOrderService.save(shopOrder);if (save) {log.info("保存订单成功,订单信息为{}",JSON.toJSONString(shopOrder));return BaseResult.success(shopOrder);}else {return BaseResult.fail("插入失败");}}
主要问题:
1. 通过DiscoveryClient的getInstances解决了硬编码问题和服务不好管理问题
2.但是还未解决集群的负载均衡问题
1.3 使用Ribbot负载均衡和RestTemplate
在RestTemplate中加入@LoadBalanced方法实现负载均衡
/*** 使用使用Ribbon负载均衡 RestTemplate进行调用* 解决硬编码问题 负债均衡问题* Ribbon默认是轮循也可以自行配置* @param pid* @return*/@ApiOperation("创建订单--使用discoveryClient和RestTemplate 自定义负载均衡 ")@GetMapping("/order/prod4/{pid}")public BaseResult createOrder4(@PathVariable("pid") Long pid) {log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息",pid);//调用商品微服务,查询商品信息BaseResult baseResult = restTemplate.getForObject("http://product-service/api/productService/product/"+pid, BaseResult.class);HashMap data = (HashMap) baseResult.getData();String mapToJson = JSON.toJSONString(data);ShopProduct shopProduct = JSON.parseObject(mapToJson, ShopProduct.class);log.info("查询到{}号商品的信息,内容是{}",pid, JSON.toJSONString(shopProduct));//创建订单ShopOrder shopOrder = new ShopOrder();shopOrder.setUid(1L);shopOrder.setUsername("admin");shopOrder.setPid(pid);shopOrder.setPname(shopProduct.getPname());shopOrder.setPprice(shopProduct.getPprice());shopOrder.setNumber(1L);//创建订单boolean save = shopOrderService.save(shopOrder);if (save) {log.info("保存订单成功,订单信息为{}",JSON.toJSONString(shopOrder));return BaseResult.success(shopOrder);}else {return BaseResult.fail("插入失败");}}
解决了硬编码问题、负载均衡问题以及不便于管理的问题,但是还有如下缺点:
1、代码可读性不好restTemplate.getForObject("http:xxxx 无法跳转方法查看
2、编码风格不同 我们一般使用的是xxxservice进行调用方法
3、需要加入@LoadBalanced 引入Ribbon 进行负载均衡,但是Feign引入了Ribbon 直接可以使用负载均衡
基于以上原因,Feign就出现了 ,用于解决以上的问题
3、Feign的使用
3.1 加入Feign依赖
<!-- Spring Cloud Feign在Hoxton.M2 RELEASED版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错,但是之前项目使用的Fegin是可以的!!!--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><exclusions><exclusion><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-loadbalancer</artifactId></dependency>
<!-- Feign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
3.2 在注解中开启Feign
@SpringBootApplication
@MapperScan("com.sofwin.mapper")
@EnableDiscoveryClient
//开启Feign
@EnableFeignClients
public class OrderApplication
3.3 创建service进行调用远程服务
@FeignClient(value = "product-service" )//服务名称
public interface ProductServiceFeign {//FeignClient和GetMapping 加起来就是url@GetMapping("/api/productService/product/{pid}")BaseResult getPorductById(@PathVariable("pid") Long pid);@GetMapping("/api/productService/product2/{pid}")ShopProduct getPorductById2(@PathVariable("pid") Long pid);
}
3.4 订单调用商品远程服务
@Autowiredprivate ProductServiceFeign productServiceFeign;/*** @param pid* @return*/@ApiOperation("创建订单--Feign ")@GetMapping("/order/prod5/{pid}")public BaseResult createOrder5(@PathVariable("pid") Long pid) {log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息",pid);//调用商品微服务,查询商品信息BaseResult baseResult = productServiceFeign.getPorductById(pid);HashMap data = (HashMap) baseResult.getData();String mapToJson = JSON.toJSONString(data);ShopProduct shopProduct = JSON.parseObject(mapToJson, ShopProduct.class);log.info("查询到{}号商品的信息,内容是{}",pid, JSON.toJSONString(shopProduct));//创建订单ShopOrder shopOrder = new ShopOrder();shopOrder.setUid(1L);shopOrder.setUsername("admin");shopOrder.setPid(pid);shopOrder.setPname(shopProduct.getPname());shopOrder.setPprice(shopProduct.getPprice());shopOrder.setNumber(1L);//创建订单boolean save = shopOrderService.save(shopOrder);if (save) {log.info("保存订单成功,订单信息为{}",JSON.toJSONString(shopOrder));return BaseResult.success(shopOrder);}else {return BaseResult.fail("插入失败");}}
3.5 重启order服务,查看效果
五、Sentinel 服务容错
1.高并发带来的问题
在微服务架构中,我们将业务拆分成一个个服务,服务与服务之间可以相互调用,但是由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。
接下来,我们模拟一个高并发的场景
1.1 编写Java代码
@Autowiredprivate ProductServiceFeign productServiceFeign;/*** Feign 创建订单* @param pid* @return*/@ApiOperation("创建订单--Feign ")@GetMapping("/test1/{pid}")public BaseResult createOrder5(@PathVariable("pid") Long pid) {log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息",pid);//调用商品微服务,查询商品信息BaseResult baseResult = productServiceFeign.getPorductById(pid);
// 模拟网速慢的情况try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}HashMap data = (HashMap) baseResult.getData();String mapToJson = JSON.toJSONString(data);ShopProduct shopProduct = JSON.parseObject(mapToJson, ShopProduct.class);log.info("查询到{}号商品的信息,内容是{}",pid, JSON.toJSONString(shopProduct));//创建订单ShopOrder shopOrder = new ShopOrder();shopOrder.setUid(1L);shopOrder.setUsername("admin");shopOrder.setPid(pid);shopOrder.setPname(shopProduct.getPname());shopOrder.setPprice(shopProduct.getPprice());shopOrder.setNumber(1L);//创建订单
// boolean save = shopOrderService.save(shopOrder);//不报错 防止垃圾数据太多log.info("创建订单成功");return BaseResult.success(null,1,"创建订单成功");}
1.2 修改配置文件中tomcat的并发数
server:tomcat:max-threads: 10 # 设置10条 默认200条太多不好测试
1.3 利用压测工具Jemeter
Apache JMeter - Apache JMeter™
①下载
②设置环境变量
③ 进入bin目录,修改jmeter.properties文件中语言支持为language=zh_CN,然后点击jmeter.bat启动软件即可
④ 编写Jemeter压测
添加线程组
添加http请求
1.4 测试
正常请求
开启jemter 线程后在进行访问,发现要等待很久才出现结果。
当我们逐渐加多线程数的时候就会发现,等待时间越来越长,如果我们处理这个业务也很慢的,并且同一时间访问的人数非常多的时候,往往就可能会崩溃,这就是服务雪崩的雏形。
2. 服务雪崩效应
在分布式系统中,由于网络原因或者自身的原因,服务一般无法保证100%可用。如果一个服务出现了问题,调用这个服务就会线程线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等待,进而导致服务瘫痪。
由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩效应”
雪崩发送的原因多种多样,有不合理的容量设计,或者是高并发下某一方法响应变慢,亦或是某台机器的资源耗尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发送问题,不会影响到其他服务的政策运行。也就是雪落而不雪崩。
3.常见的容错方案
要防止雪崩的扩散,我们就要做好服务的容错,容错说白了就是保护自己不被猪队友拖垮的一些措施。
常见的容错思路
隔离、超时、限流、熔断、降级这几种
3.1 隔离
它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故
障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的
系统服务。
3.2 超时
在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,
就断开请求,释放掉线程。
3.3 限流
限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到
的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的(防止被上游服务搞崩)
3.4 熔断
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整
体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。(防止被下游服务搞崩)
服务熔断一般有三种状态:
- 熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制
- 熔断开启状态(Open)
后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
- 半熔断状态(Half-Open)
尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预
期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。
3.5 降级
降级其实就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案
4.常见的容错组件
- Hystrix
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。
- Resilience4J
Resilicence4J一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystrix官方推荐的替代产品。不仅如此, Resilicence4j还原生支持Spring Boot 1.x/2.x,而且监控也支持和
prometheus等多款主流产品进行整合。
- Sentinel
Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。
Sentinel | Hystrix | resilience4j | |
隔离策 略 | 信号量隔离(并发线程数限流) | 线程池隔离/信 号量隔离 | 信号量隔离 |
熔断降 级策略 | 基于响应时间、异常比率、异常数 | 基于异常比率 | 基于异常比率、响应 时间 |
实时统 计实现 | 滑动窗口(LeapArray) | 滑动窗口(基 于 RxJava) | Ring Bit Buffer |
动态规 则配置 | 支持多种数据源 | 支持多种数据 源 | 有限支持 |
扩展性 | 多个扩展点 | 插件的形式 | 接口的形式 |
基于注 解的支 持 | 支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 | Rate Limiter |
流量整 形 | 支持预热模式、匀速器模式、预热排队 模式 | 不支持 | 简单的 Rate Limiter 模式 |
系统自 适应保 护 | 支持 | 不支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可配置规则、 查看秒级监控、机器发现等 | 简单的监控查 看 | 不提供控制台,可对 接其它监控系统 |
5、什么是Sentinel
Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量
为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
Sentinel 具有以下特征:
丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即
突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用
应用等。
完备的实时监控: Sentinel 提供了实时的监控功能。通过控制台可以看到接入应用的单台机器秒
级数据, 甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块, 例如与 SpringCloud、 Dubbo、 gRPC 的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 分为两个部分:
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo /
Spring Cloud 等框架也有较好的支持。
控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等
应用容
5.1 快速入门
在springcloud中继承Sentinel非常简单,只需要加入Sentinel依赖即可
1.在pom中加入下面依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
2.编写一个Controller测试使用
@RestController
@Api("订单模块 Sentinel")
@Slf4j
public class OrderController3 {@ApiOperation("message1")@GetMapping("/message1")public String createOrder8() {return "message1" + DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");}@ApiOperation("message2")@GetMapping("/message2")public String test3() {return "message2"+DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss";}
}
5.2 使用Sentinel控制台
Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能。
①下载
下载地址:Releases · alibaba/Sentinel · GitHubhttps://github.com/alibaba/Sentinel/releases
下载jar包即可
②启动
在jar包路径进入cmd启动
# 直接使用jar命令启动项目(控制台本身是一个SpringBoot项目)
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.5.jar
③yml中配置
spring:cloud:sentinel:transport:port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可dashboard: localhost:8080 # 指定控制台服务的地址
④通过浏览器访问localhost:8080 进入控制台 ( 默认用户名密码是 sentinel/sentinel )
Sentinel的控制台其实就是一个SpringBoot编写的程序。我们需要将我们的微服务程序注册到控制台上,即在微服务中指定控制台的地址, 并且还要开启一个跟控制台传递数据的端口, 控制台也可以通过此端口调用微服务中的监控程序获取微服务的各种信息。 (yml中设置)
5.3 实现一个接口的限流
1.首先先访问一下 http://localhost:8091/api/orderService/message1
2.设置限流规则
3.快速访问这个接口
QPS是一秒钟访问的次数
5.4 Sentinel的概念和功能
- 资源
资源就是Sentinel要保护的东西
它可以是Java应用程序中的任何内容,可以是一个服务,也可以是一个方法,甚至可以是一段代码
入门案例中message1方法就是一个资源
- 规则
规则就是用来定义如何进行保护资源的
作用在资源之上,定义以什么样的方式保护资源,主要包括流量控制规则、熔断降级、热点授权以及系统保护柜子
入门案例中message1资源设置了一种流控规则,限制了进入message1的流量
5.5 重要规则
Sentinel的主要功能就是容错,主要体现为下面这三个:
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是
随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。
Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
熔断降级
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则
对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
Sentinel 对这个问题采取了两种手段:
- 通过并发线程数进行限制
Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。当某个资源
出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆
积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的
线程完成任务后才开始继续接收请求。
- 通过响应时间对资源进行降级
除了对并发线程数进行控制以外, Sentinel 还可以通过响应时间来快速降级不稳定的资源。
当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的
时间窗口之后才重新恢复
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。当系统负载较高的时候,如果还持续让
请求进入可能会导致系统崩溃,无法响应。在集群环境下,会把本应这台机器承载的流量转发到其
它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候, Sentinel 提供了对应的保
护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请
求。
总之一句话: 我们需要做的事情,就是在Sentinel的资源上配置各种各样的规则,来实现各种容错的功能
6、Sentinel的规则
1.流控规则
流量控制,其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时
对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
资源名:唯一名称,默认是请求路径,可自定义
针对来源:指定对哪个微服务进行限流,默认指default,意思是不区分来源,全部限制
阈值类型/单机阈值:
QPS(每秒请求数量) : 当调用该接口的QPS达到阈值的时候,进行限流线程数:当调用该接口的线程数达到阈值的时候,进行限流
是否集群:
1.1 简单配置:
QPS和并发线程数 (上面message1方法一样)
1.2 高级配置 流控模式
sentinel共有三种流控模式,分别是:
- 直接(默认):接口达到限流条件时,开启限流
- 关联:当关联的资源达到限流条件时,开启限流 [适合做应用让步]
- 链路:当从某个接口过来的资源达到限流条件时,开启限流
下面呢分别演示三种模式:
直接流控模式
直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。上面案例使用的就是直接流控模式。
关联流控模式
关联流控模式指的是,当指定接口关联的接口达到限流条件时,开启对指定接口开启限流。
第1步:配置限流规则, 将流控模式设置为关联,关联资源设置为的 /order/message2。
第2步:通过jemeter软件向message2连续发送请求,注意QPS一定要大于3
第3步:访问/api/orderService/message1,会发现已经被限流
链路流控模式
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项,区别在于: 针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。
第1步: 编写一个service,在里面添加一个方法message
@Service
public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder>implements ShopOrderService {@SentinelResource("message")public void message() {System.out.println("message");}
}
第2步: 在Controller中声明两个方法,分别调用service中的方法m
@RestController
@Api("订单模块 Sentinel")
@Slf4j
public class OrderController3 {@Autowiredprivate ShopOrderServiceImpl shopOrderService;@ApiOperation("message1")@GetMapping("/message1")public String createOrder8() {shopOrderService.message();return "message1" + DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");}@ApiOperation("message2")@GetMapping("/message2")public String test3() {shopOrderService.message();return "message2"+DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");}
}
第3步: 禁止收敛URL的入口 context
从1.6.3 版本开始, Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。
1.7.0 版本开始(对应SCA的2.1.1.RELEASE),官方在CommonFilter 引入了
WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context。将其配置为 false 即可根据不同的URL 进行链路限流。SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即可关闭收敛(我们是)
(1)我们用的是2021.0.1.0 完全够用
(2)配置文件中关闭sentinel的CommonFilter实例化
spring:cloud:sentinel:transport:port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可dashboard: localhost:8080 # 指定控制台服务的地址web-context-unify: false # 关闭context整合 注意低版本可能没有 spring-cloud-alibba 2.2.5.RELEASE
(3) 控制台配置限流规则
(4) 分别通过 /order/message1 和 /order/message2 访问, 发现2没问题, 1的被限流了
1.3 高级配置 流控效果
- 快速失败(默认) : 直接失败,抛出异常,不做任何额外的处理,是最简单的效果
- Warm Up:它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景。
- 排队等待:让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。
warm up
修改流控规则
通俗一点讲就是我们设置的阈值是10,预热时长是5s。那么当大量请求同时涌入系统时,系统会经过5秒钟的预热才会一次处理10个请求,在5秒钟之内先期可能会处理3个、5个、8个再至10个
我们修改线程组线程数为1s内将15个线程启动,一直发送。如下图所示:
在刚开始的几秒中内每次只请求三次,之后慢慢变大,在5秒后到达设置阈值
排队等待
jemeter不变,控制台每秒打印输出的次数就是我们设置的阈值,这就是匀速排队的效果。
2.熔断规则
熔断规则就是设置当满足什么条件的时候,对服务进行熔断。
Sentinel提供了三个衡量条件:
2.1 平均响应时间 :
当资源的平均响应时间超过阈值(以 ms 为单位)之后,资源进入准降级状态。
如果接下来 1s 内持续进入 5 个请求,它们的 RT都持续超过这个阈值,那么在接下的时间窗口
(以 s 为单位)之内,就会对这个方法进行服务降级。
设置熔断策略,1000ms 的请求数>5 并且这些请求的RT>300(即最大的响应时间) 并且大于比例阈值0.1(10%)触发熔断
启动后发现
这个时候就进入熔断,让后将jemeter关闭,等待5s进入半熔断状态,当在发起一个请求的时候如过不超过这个时间,就恢复为荣孤单关闭情况,请求成功
2.2 异常比例:
当资源的每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的时间窗口(以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0,1.0]。
第1步: 首先模拟一个异常
//熔断 异常比例 和异常数的测试int i = 0;@ApiOperation("message3")@GetMapping("/message3")public String test4() {i++;//0.333if(i % 3 == 0) {throw new ArrayIndexOutOfBoundsException();}return "message3";}
第2步: 设置异常比例为0.25
第3步访问
停止后5秒在访问就可以访问了
2.3 异常数 :
当资源近 1 分钟的异常数目超过阈值之后会进行服务降级。注意由于统计时间窗口是分钟级别的,若时间窗口小于 60s,则结束熔断状态后仍可能再进入熔断状态
注意:使用sentinel过程中发现,如果使用springboot的@RestControllerAdvice
全局异常捕获,那么设置sentinel的异常数熔断规则就会失效
需要加入这样一句话
3. 热点规则
热点参数流控规则是一种更细粒度的流控规则, 它允许将规则具体到参数上
3.1 热点规则简单使用
第1步: 编写代码
@ApiOperation("message4")@GetMapping("/message4")//注意热点数据必须使用下面注解才能生效@SentinelResource("message4")public String test5(String name ,Integer age) {String str = "名字:%s,年龄%d";return String.format(str,name,age) ;}
第2步: 配置热点规则
对message4这个资源的0号参数(第一个参数)做统计,每1秒相同参数值的请求数不能超过1
第3步: 分别用两个参数访问,会发现只对第一个参数限流了
3.2 热点规则增强使用
参数例外项允许对一个参数的具体值进行流控,编辑刚才定义的规则,增加参数例外项
4.授权规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:
若配置白名单,则只有请求来源位于白名单内时才可通过;
若配置黑名单,则请求来源位于黑名单时不通过,其余的请求通过
上面的资源名和授权类型不难理解,但是流控应用怎么填写呢?
其实这个位置要填写的是来源标识, Sentinel提供了 RequestOriginParser 接口来处理来源。
只要Sentinel保护的接口资源被访问, Sentinel就会调用 RequestOriginParser 的实现类去解析访问来源
第1步: 自定义来源处理规则
package com.sofwin.config;import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.sofwin.exception.MyException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;/*** @packageName: com.sofwin.config* @author: wentao* @date: 2023/9/18 10:13* @version: 1.0* @email 1660420659@qq.com* @description: Sentinel 授权规则中 流控应用的名称设置*/
//@Component
//public class RequestOriginParserDefinition implements RequestOriginParser {
// @Override
// public String parseOrigin(HttpServletRequest request) {
// //这里我们使用获取serviceName来作为流控应用的名称 ---以后可以设置请求头或者什么都行
// String serviceName = request.getParameter("serviceName");
// if (StringUtils.isEmpty(serviceName)) {
// throw new MyException("serviceName is not empty",500);
// }
// return serviceName;
// }
//}
第2步: 授权规则配置
第3步: 访问 http://localhost:8091/order/message1?serviceName=Service-A观察结果
5.系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、 RT、入口 QPS 、 CPU使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。
- Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
- RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
- CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护。
6.自定义异常返回
因为有限流、熔断、热点参数、授权、系统等流量控制,每次抛出的提示信息都一样,就不能很好的区分,以及后期进行排查,因此设置自定义异常返回
package com.sofwin.config;import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sofwin.exception.MyException;
import com.sofwin.result.BaseResult;
import org.bouncycastle.asn1.ocsp.ResponseData;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @packageName: com.sofwin.config* @author: wentao* @date: 2023/9/18 15:08* @version: 1.0* @email 1660420659@qq.com* @description: sentinel的自定义异常页面*/
@Component
public class ExceptionHandlerPage implements BlockExceptionHandler {//BlockException 异常接口,包含Sentinel的五个异常4.7 @SentinelResource的使用// FlowException 限流异常// DegradeException 熔断异常// ParamFlowException 参数限流异常// AuthorityException 授权异常// SystemBlockException 系统负载异常@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {if (e instanceof FlowException) {throw new MyException("限流了",300) ;} else if (e instanceof DegradeException) {throw new MyException("熔断",300) ;} else if (e instanceof ParamFlowException) {throw new MyException("热点参数限流",300) ;} else if (e instanceof SystemBlockException) {throw new MyException("系统规则(负载/...不满足要求",300) ;} else if (e instanceof AuthorityException) {throw new MyException("授权规则不通过",300) ;}}
}
然后在全局异常处理类中进行捕获,进行返回即可
7.@SentinelResource的使用
在定义了资源点之后,我们可以通过Dashboard来设置限流和降级策略来对资源点进行保护。同时还能通过@SentinelResource来指定出现异常时的处理策略。(接口使用,例如message1中调用message,使用链路的流控模式就无法进行捕获,因此使用这个注解)
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。其主要参数如下:
属性 | 作用 |
value | 资源名称 |
entryType | entry类型,标记流量的方向,取值IN/OUT,默认是OUT |
blockHandler | 处理BlockException的函数名称,函数要求: 1. 必须是 public 2.返回类型 参数与原方法一致 3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。 |
blockHandlerClass | 存放blockHandler的类,对应的处理函数必须static修饰。 |
fallback | 用于在抛出异常的时候提供fallback处理逻辑。 fallback函数可以针对所 有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进 行处理。函数要求: 1. 返回类型与原方法一致 2. 参数类型需要和原方法相匹配 3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定fallbackClass里面的方法。 |
fallbackClass | 存放fallback的类。对应的处理函数必须static修饰。 |
defaultFallback | 用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进 行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函 数要求: 1. 返回类型与原方法一致 2. 方法参数列表为空,或者有一个 Throwable 类型的参数。 3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定 fallbackClass 里面的方法。 |
exceptionsToIgnore | 指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入 fallback逻辑,而是原样抛出。 |
exceptionsToTrace | 需要trace的异常 |
第一步编写注解
package com.sofwin.service.impl;import com.alibaba.csp.sentinel.annotation.SentinelResource;import com.sofwin.config.OrderMessageServiceBlockHandler;
import com.sofwin.config.OrderMessageServiceFallbackHandler;
import org.springframework.stereotype.Service;/*** @packageName: com.sofwin.service.impl* @author: wentao* @date: 2023/9/16 19:38* @version: 1.0* @email 1660420659@qq.com* @description: TODO*/
@Service
public class MessageServiceImpl {int i = 0;//value对应的页面设置的@SentinelResource(value = "message",blockHandlerClass = OrderMessageServiceBlockHandler.class,blockHandler = "messageServiceBlockHandler",fallbackClass = OrderMessageServiceFallbackHandler.class,fallback = "messageServiceFallbackHandler")public String message() {i++;//0.333if(i % 3 == 0) {throw new ArrayIndexOutOfBoundsException();}return "message";}}
第二步写controller
@Autowiredprivate MessageServiceImpl messageService;@ApiOperation("message22")@GetMapping("/message22")public String test322() {messageService.message();return "message22"+DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");}
第三步设置流控规则
8.Feign整合Sentinel
第1步: 引入sentinel的依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
第2步: 在配置文件中开启Feign对Sentinel的支持
feign:sentinel:enabled: true
第3步: 创建容错类
package com.sofwin.service.fallback;import com.sofwin.domain.ShopProduct;
import com.sofwin.result.BaseResult;
import com.sofwin.service.ProductServiceFeign;
import org.springframework.stereotype.Service;/*** @packageName: com.sofwin.service.fallback* @author: wentao* @date: 2023/9/24 9:22* @version: 1.0* @email 1660420659@qq.com* @description: ProductServiceFeign中 Feign调用失败 Sentinel的容错方案* 一旦Feign远程调用出现问题了,就会进入当前类中同名方法,执行容错逻辑*/@Service
public class ProductServiceFeignFallBack implements ProductServiceFeign {@Overridepublic BaseResult getPorductById(Long pid) {ShopProduct shopProduct = new ShopProduct();shopProduct.setPid(-100L);return BaseResult.success(shopProduct);}//测试 feign出错调用容错类中这个方法@Overridepublic ShopProduct getPorductById2(Long pid) {ShopProduct shopProduct = new ShopProduct();shopProduct.setPid(-100L);return shopProduct;}
}
第4步: 为被容器的接口指定容错类
package com.sofwin.service;import com.sofwin.domain.ShopProduct;
import com.sofwin.result.BaseResult;import com.sofwin.service.fallback.ProductServiceFeignFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//restTemplate.getForObject("http://product-service/api/productService/product/"+pid, BaseResult.class);
/*** @packageName: com.sofwin.service* @author: wentao* @date: 2023/9/16 15:34* @version: 1.0* @email 1660420659@qq.com* @description: Feign -productService* fallback和fallbackFactory同时只能有一个 都是Sentinel的容错方案*/
@FeignClient(value = "product-service" //服务名称, fallback = ProductServiceFeignFallBack.class //feign错误后调用的容错类方案
// ,fallbackFactory = ProductServiceFeignFallBackFactory.class
)
public interface ProductServiceFeign {//FeignClient和GetMapping 加起来就是url@GetMapping("/api/productService/product/{pid}")BaseResult getPorductById(@PathVariable("pid") Long pid);@GetMapping("/api/productService/product2/{pid}")ShopProduct getPorductById2(@PathVariable("pid") Long pid);
}
第5步: 修改controller
//测试feign出错后,sentinel的容错@ApiOperation("创建订单--Feign6 ")@GetMapping("/order/prod6/{pid}")public BaseResult createOrder6(@PathVariable("pid") Long pid) {log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息",pid);//调用商品微服务,查询商品信息ShopProduct shopProduct = productServiceFeign.getPorductById2(pid);if (shopProduct.getPid() == -100L) {//如果100说明调用了Sentinel的容错机制ShopOrder shopOrder = new ShopOrder();shopOrder.setOid(-100L);shopOrder.setPid(shopProduct.getPid());shopOrder.setPname(shopProduct.getPname());return BaseResult.success(shopOrder);}log.info("查询到{}号商品的信息,内容是{}",pid, JSON.toJSONString(shopProduct));//创建订单ShopOrder shopOrder = new ShopOrder();shopOrder.setUid(1L);shopOrder.setUsername("admin");shopOrder.setPid(pid);shopOrder.setPname(shopProduct.getPname());shopOrder.setPprice(shopProduct.getPprice());shopOrder.setNumber(1L);//创建订单boolean save = shopOrderService.save(shopOrder);if (save) {log.info("保存订单成功,订单信息为{}",JSON.toJSONString(shopOrder));return BaseResult.success(shopOrder);}else {return BaseResult.fail("插入失败");}}
第六步 http://localhost:8091/api/orderService/order/prod6/2
如果调动远程失败(将商品服务关闭)
调用容错方案成功
扩展: 如果想在容错类中拿到具体的错误,可以使用下面的方式
第1步: 修改ProductServiceFeign
package com.sofwin.service;import com.sofwin.domain.ShopProduct;
import com.sofwin.result.BaseResult;import com.sofwin.service.fallback.ProductServiceFeignFallBack;
import com.sofwin.service.fallback.ProductServiceFeignFallBackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//restTemplate.getForObject("http://product-service/api/productService/product/"+pid, BaseResult.class);
/*** @packageName: com.sofwin.service* @author: wentao* @date: 2023/9/16 15:34* @version: 1.0* @email 1660420659@qq.com* @description: Feign -productService* fallback和fallbackFactory同时只能有一个 都是Sentinel的容错方案*/
@FeignClient(value = "product-service" //服务名称
// , fallback = ProductServiceFeignFallBack.class //feign错误后调用的容错类方案,fallbackFactory = ProductServiceFeignFallBackFactory.class
)
public interface ProductServiceFeign {//FeignClient和GetMapping 加起来就是url@GetMapping("/api/productService/product/{pid}")BaseResult getPorductById(@PathVariable("pid") Long pid);@GetMapping("/api/productService/product2/{pid}")ShopProduct getPorductById2(@PathVariable("pid") Long pid);
}
第二步 创建类
package com.sofwin.service.fallback;import com.sofwin.domain.ShopProduct;
import com.sofwin.result.BaseResult;
import com.sofwin.service.ProductServiceFeign;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Service;/*** @packageName: com.sofwin.service.fallback* @author: wentao* @date: 2023/9/24 9:22* @version: 1.0* @email 1660420659@qq.com* @description:* FallbackFactory<ProductServiceFeign> 可以在容错失败的时候记录异常 方便后期进行维护*/@Service
@Slf4j
public class ProductServiceFeignFallBackFactory implements FallbackFactory<ProductServiceFeign> {//编写匿名内部类 重写ProductServiceFeign中的方法@Overridepublic ProductServiceFeign create(Throwable cause) {return new ProductServiceFeign() {@Overridepublic BaseResult getPorductById(Long pid) {log.error("{}",cause);ShopProduct shopProduct = new ShopProduct();shopProduct.setPid(-100L);return BaseResult.success(shopProduct);}@Overridepublic ShopProduct getPorductById2(Long pid) {log.error("{}",cause);ShopProduct shopProduct = new ShopProduct();shopProduct.setPid(-100L);return shopProduct;}};}
}
第三步 http://localhost:8091/api/orderService/order/prod6/2
日志打印出来错误了
注意: fallback和fallbackFactory只能使用其中一种方式