微服务之服务降级方案集锦
ribbon 服务到服务调用 客户端负载均衡器
openfeign 服务到服务调用 内置ribbon 使用接口方式进行远程服务调用
2,本章重点
服务雪崩(概念,原因,解决办法) hystrix(熔断功能,简化版) sentinel (限流,降级,熔断)
3,具体内容
3.1 服务雪崩
3.1.1 概念:
在微服务调用的过程中由于各服务之间的强依赖关系,在服务的扇出过程中,如果某些(某一个)服务发成故障,可能会级联导致所有服务的所有资源不可用的现象,就叫做服务雪崩。
3.1.2 原因:
服务提供者不可用(硬件故障,程序 BUG,缓存击穿,用户突然大量请求等)
重试加大流量(用户重试,代码逻辑重试)
服务消费者不可用(同步等待造成的资源耗尽)
3.1.3 解决办法:
服务扩容: 增加出现问题服务的数量或者提高服务器规格
请求缓存:支持将返回结果做缓存处理;
服务限流: 限制并发的请求访问量,超过阈值则拒绝 关闭重试
服务熔断: 牺牲局部服务(下游服务),保全整体系统稳定性的措施;
服务降级: 服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑;
服务降级是指 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务。
资源有限,而请求是无限的。如果在并发高峰期,不做服务降级处理,一方面肯定会影响整体服务的性能,严重的话可能会导致宕机,某些重要的服务不可用。
3.2 hystrix
3.2.1 简介
熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
3.2.2 整合准备
模拟营销服务中新闻管理要调用订单服务中的订单列表
1)创建表,插入模拟数据
create table tb_news(
id int PRIMARY key auto_increment,
title varchar(50),
content varchar(500),
addtime date,
clicknum int
);
INSERT INTO `tb_news` (`id`, `title`, `content`, `addtime`, `clicknum`) VALUES ('1', '1', '1', '2021-01-14', '1');
INSERT INTO `tb_news` (`id`, `title`, `content`, `addtime`, `clicknum`) VALUES ('2', '2', '2', '2021-01-14', '2');
2)创建项目,Easycode生成代码
需要演示ribbon和openfeign两种熔断,所以创建两个项目分别是
market_server_ribbon_hystrix(14141)
market_server_openfeign_hystrix(14142)
启动类配置:
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.aaa.ms.dao")
application.yml配置:
#当前服务端口号
server:
port: 12222
#阿里druid连接池配置
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/qy121?useUnicode=true&characterEncoding=utf8
username: root
password: root
initial-size: 5
max-active: 20
min-idle: 10
max-wait: 10
#应用名称,向注册中心注册时非常有用
application:
name: market-server-ribbon
#nacos客户端配置
cloud:
nacos:
discovery:
server-addr: 192.168.0.102:8848
#mybatis配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# mapper-locations:
# type-aliases-package:
#swagger配置
swagger:
base-package: com.aaa.ms.controller
title: "swagger标题"
description: "描述"
version: "3.0"
contact:
name: "AAA"
email: "test@163.com"
url: "https://www.baidu.com"
terms-of-service-url: "服务条款:https://www.baidu.com"
#eureka客户端配置
#eureka:
# client:
# service-url:
# defaultZone: http://localhost:11111/eureka/
3)引入JAR
加入到micro_services的pom.xml 因为所有子项目都可能用到
<!--hystrix包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3.2.2 ribbon整合hystrix
整合ribbon代码,参考前面课件
1)启动类新加:
@EnableCircuitBreaker //开启断路器功能
2) 熔断代码:
接口:
package com.aaa.ms.service;
import com.aaa.core.bo.Order;
import com.aaa.core.util.Result;
import java.util.List;
/**
* @ fileName:OrderService
* @ description:
* @ author:zhz
* @ createTime:2021/1/14 11:13
*/
public interface OrderService {
/**
* 通过主键查询单条数据
*
* @param id 主键
* @return 单条数据
*/
Result selectOne(Integer id);
/**
* 通过实体作为筛选条件查询
* json 字符串->order 对象
* @return 对象列表
*/
Result queryAll();
}
实现类:
package com.aaa.ms.service.impl;
import com.aaa.core.bo.Order;
import com.aaa.core.util.Result;
import com.aaa.ms.service.OrderService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
/**
* @ fileName:OrderServiceImpl
* @ description:
* @ author:zhz
* @ createTime:2021/1/14 11:15
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
@Override
public Result selectOne(Integer id) {
return restTemplate.getForObject("http://order-server/order/selectOne",Result.class);
}
@Override
@HystrixCommand(fallbackMethod = "queryAllFallback")
public Result queryAll() {
return restTemplate.getForObject("http://order-server/order/selectAll?current=1&size=2", Result.class);
}
/**
* 查询所有的订单的fallback方法
* @return
*/
private Result queryAllFallback(){
Result result =new Result();
result.setCode(500);
result.setMessage("远程订单列表出现故障,请与管理员联系!");
return result;
}
}
本地服务代码:
package com.aaa.ms.service.impl;
import com.aaa.core.util.Result;
import com.aaa.ms.service.OrderService;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.aaa.ms.dao.NewsDao;
import com.aaa.ms.entity.News;
import com.aaa.ms.service.NewsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* (News)表服务实现类
*
* @author makejava
* @since 2022-02-20 20:31:44
*/
@Service("newsService")
public class NewsServiceImpl extends ServiceImpl<NewsDao, News> implements NewsService {
@Resource
private OrderService orderService;
@Override
public Map selectNewsAndOrders() {
List<News> newsList = this.baseMapper.selectList(null);
Result result = orderService.queryAll();
Map map = new HashMap();
map.put("orderResult",result);
map.put("newsList",newsList);
return map;
}
}
controller方法:
/**
* 分页查询所有数据
*
* @return 所有数据
*/
@GetMapping("selectNewsAndOrders")
public R selectNewsAndOrders() {
return success(this.newsService.selectNewsAndOrders());
}
3.2.3 feign整合hystrix
1)启动类新加
@EnableFeignClients //开启feign客户端
2) 熔断代码:
接口:
package com.aaa.ms.service;
import com.aaa.core.bo.Order;
import com.aaa.core.util.Result;
import com.aaa.ms.service.impl.RemoteOrderServiceFallback;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @ fileName:RemoteOrderService
* @ description:
* @ author:zhz
* @ createTime:2022/2/20 22:56
* @ version:1.0.0
*/
@FeignClient(name = "order-server",fallback = RemoteOrderServiceFallback.class)
public interface RemoteOrderService {
/**
* 远程调用
* @param id
* @return
*/
@GetMapping("/order/selectOne")
Result selectAll(@RequestParam("id") Integer id);
}
fallback代码:
package com.aaa.ms.service.impl;
import com.aaa.core.util.Result;
import com.aaa.ms.service.RemoteOrderService;
import org.springframework.stereotype.Service;
/**
* @ fileName:RemoteOrderServiceFallback
* @ description:
* @ author:zhz
* @ createTime:2022/2/20 23:32
* @ version:1.0.0
*/
@Service
public class RemoteOrderServiceFallback implements RemoteOrderService {
@Override
public Result selectAll(Integer id) {
Result result=new Result();
result.setCode(500);
result.setMessage("远程服务器无法调用!");
return result;
}
}
服务方法:
@Override
public News getById(Serializable id) {
News news = baseMapper.selectById(id);
Result result = orderService.selectAll(2);
news.setResult(result);
return news;
}
3)application.yml添加:
#开启feign熔断功能
feign:
hystrix:
enabled: true
3.3 sentinel
3.3.1 概述
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
简介:
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
特点:
• 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
• 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
• 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
• 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
主要特性
开源生态:
两个部分:
• 核心库(Java 服务端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
• 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
3.3.2 sentinel控制台(服务端)
https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
概述:
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。
功能:
查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
规则管理和推送:统一管理推送规则。
鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
下载启动:
下载地址:
https://github.com/alibaba/Sentinel/releases
启动:
注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.3.jar
访问:
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel
3.3.3 sentinel 客户端接入控制台(springcloud使用sentinel)
SQL语句:
create table tb_news(
id int PRIMARY key auto_increment,
title varchar(50),
content varchar(500),
addtime date,
clicknum int,
product_id int
);
INSERT INTO `tb_news` (`id`, `title`, `content`, `addtime`, `clicknum`) VALUES ('1', '1', '1', '2021-01-14', '1',1);
INSERT INTO `tb_news` (`id`, `title`, `content`, `addtime`, `clicknum`) VALUES ('2', '2', '2', '2021-01-14', '2',2);
新建微服务market_server_ribbon_sentinel :
反向工程生成代码, 讲解sentienl基本用法,以及远程服务调用和ribbon 以及和openfeign整合
引入jar:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置控制台:
spring:
cloud:
#sentinel客户端配置
sentinel:
transport:
port: 8719
dashboard: localhost:8080
这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。
触发客户端初始化:
确保客户端有访问量(一定要有请求),Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。
http://localhost:14840/news/queryById?id=1
http://localhost:5555/swagger-ui/index.html 使用swagger 访问查询单个和分页查询方法,动态观察实时监控的变化
3.3.3 流量控制
1)概述
其原理是监控应用流量的 QPS(queries per second) 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
resource:资源名,即限流规则的作用对象(唯一的)
count(单机阈值): 限流阈值
grade: 限流阈值类型(QPS 或并发线程数)
limitApp(针对来源): 流控针对的调用来源,若为 default 则不区分调用来源
strategy: 调用关系限流策略
controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
2)流量规则:
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
QPS-直接-快速失败测试(默认):
超过配置的QPS直接限流
QPS一般指每秒查询率。 每秒查询率(QPS,Queries-per-second)是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
http://localhost:5555//product/selectAll?current=1&size=10
一秒钟内多次刷新该请求,观察效果 1秒内刷新两次以上,会Blocked by Sentinel (flow limiting)的错误
并发线程数-直接-快速失败测试:
超过配置的并发线程数,直接限流
修改为并发线程数,单机阈值可以随便设置
然后使用jmeter测试,模拟某一时刻并发多少次操作,会发现一旦超过设置的单机阈值,就会出错
jmeter使用参考文档:https://shimo.im/docs/qpYVQG8hcjrcXtc3
Ramp-up time是规定所有用户在时间段内把请求发送完
http://localhost:5555//product/selectAll?current=1&size=10
QPS-关联-快速失败测试:
当下游被调用的服务达到阈值,直接影响当前服务的调用,让当前请求限流。
复制selectOne方法,创建selectOneA和selectOneB方法
让A方法调用B方法,然后对B限流 并达到限流的阈值,A方法无法访问
jmeter 模拟请求selectOneB方法
jmeter开启前,可以访问selectOneA方法 jmeter开启后无法访问
QPS-链路-快速失败测试:(测试失败)
多条链路关联同一资源时,限制某一链路入口,而不限制其他入口。
服务层代码:
@Override
@SentinelResource(value = "getById",fallback = "getByidFallback")
public Order getById(Serializable id) {
return baseMapper.selectById(id);
}
public Order getByidFallback(Serializable id, BlockException e){
Order order = new Order();
order.setOrderSn("error");
order.setId(444);
return order;
}
控制层代码:
@GetMapping("selectOneAA")
@ApiOperation("根据编号查询")
public Result selectOneAA(@RequestParam("id") Integer id) {
Order order = this.orderService.getById(id);
order.setOrderSn("sn333");
System.out.println("selectOneAA在执行");
return success(order);
}
@GetMapping("selectOneBB")
@ApiOperation("根据编号查询")
public Result selectOneBB(@RequestParam("id") Integer id) {
Order order = this.orderService.getById(id);
order.setOrderSn("sn333");
System.out.println("selectOneBB在执行");
return success(order);
}
发现selectOneBB方法没有问题 而另外一个方法被限流
http://localhost:2223/order/selectOneAA?id=1
服务接口,有两个端都进行调用,一个PC端,一个mobile端 。
3)流程控制效果
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
直接拒绝
(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
Warm Up
(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8
因为默认coldFactor=3 所以一开始阈值就是10/3=3 经过20秒后,达到配置的10
http://localhost:2223/order/selectOne?id=1
快速刷新该请求,做测试,发现前20秒会出错,后面不会。。。
匀速排队
(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
后台方法改造,加上日志打印:
/**
* 通过主键查询单条数据
*
* @param id 主键
* @return 单条数据
*/
@GetMapping("selectOne")
@ApiOperation("根据编号查询")
public Result selectOne(@RequestParam("id") Integer id) {
log.info("执行根据编号查询-------------------------------------");
Order order = this.orderService.getById(id);
order.setOrderSn("sn333");
return success(order);
}
http://localhost: 2223 / o rd er /select On e ? i d =1
多次执行,查看日志发现后面是一秒钟执行一次,不管你请求速度是多快。。。。
3.3.4 服务降级
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
1)概念:
2)熔断策略:
• 慢调用比例
(SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(response time即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
修改代码:
/**
* 通过主键查询单条数据(演示服务降级)
* @param id 主键
* @return 单条数据
*/
@GetMapping("selectOneB/{id}")
public Result selectOneB(@PathVariable Serializable id) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("测试慢调用比例");
return success("模拟商品按照编号查询");
}
设置规则:
使用jmeter测试:
在1秒(统计时长)内,请求数目(5)的比例阈值(0.5)的响应时长(1秒)大于配置(200毫米),就会在接下来的10秒中出现熔断!
• 异常比例
(ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
修改代码:
/**
* 通过主键查询单条数据(演示服务降级)
* @param id 主键
* @return 单条数据
*/
@GetMapping("selectOneB/{id}")
public Result selectOneB(@PathVariable Serializable id) {
log.info("测试异常比例");
String str = null;
log.info(str.length());
return success("模拟商品按照编号查询");
}
设置规则:
使用jmeter进行测试(同上)
进行请求测试:
http://localhost:5555//product/selectOneB/1
请求数大于阈值并且异常比例大于配置异常比例(使用jmeter时,正常保护,不使用时,直接报错500. )
• 异常数
(ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
修改代码:
/**
* 通过主键查询单条数据(演示服务降级)
* @param id 主键
* @return 单条数据
*/
@GetMapping("selectOneB/{id}")
public Result selectOneB(@PathVariable Serializable id) {
log.info("测试异常数");
String str = null;
log.info(str.length());
return success("模拟商品按照编号查询");
}
设置规则:
30秒内,请求数大于1,错误3次,熔断10秒。
直接请求方法多次,查看效果http://localhost:5555//product/selectOneB/1
3.3.5 热点参数限流
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
1)概念
很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
2) 实例
修改代码:
/**
* 通过主键查询单条数据(测试热点数据限流)
*
* @param id 主键
* @return 单条数据
*/
@GetMapping("queryByIdC")
@SentinelResource(value = "queryByIdCCCC",blockHandler = "queryByIdCBlockHandler" )
public Result queryByIdC(@RequestParam(value = "id",required = false) Integer id) {
return success("请求成功");
}
/**
*限流异常处理
* @param id
* @return
*/
public Result queryByIdCBlockHandler(Integer id,BlockException e){
return error("原方法请求带参数限流失败!!");
}设置配置:
注意:资源名设置的是@SentinelResource 注解中的value值,错误无法测试
测试:
http://localhost:5555//product/selectOneC 不带参数多次刷新
http://localhost:5555//product/selectOneC ?id=1 带参数一秒刷新两次或者以上看效果
QPS大于1次/秒 限流
例外配置项目:
测试:
http://localhost:5555//product/selectOneC ?id=1 带参数一秒刷新两次或者以上看效果
QPS大于1次/秒 限流
http://localhost:5555//product/selectOneC ?id=110 带参数一秒刷新两次或者以上看效果
QPS大于1次/秒 不限流因为设置的200
3.3.6 系统自适应限流
1)概念:
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
2)规则:
• Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
• CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
• 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
• 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
• 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
3)实例:
使用jmeter测试 修改阈值再次测试!
使用浏览器刷新请求,一秒超过2次会出错!
3.3.7 ribbon整合sentinel(项目实战)
https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
以 MarketService使用ribbon远程调用OrderServer为例,整合sentinel测试
@SentinelResource 注解 用法
注意:注解方式埋点不支持 private 方法。
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
• value:资源名称,必需项(不能为空)
• entryType:entry 类型,可选项(默认为 EntryType.OUT)
• blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
• fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
• 返回值类型必须与原函数返回值类型一致;
• 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
• fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
• defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
• 返回值类型必须与原函数返回值类型一致;
• 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
• defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
• exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
远程服务代码及配置:
package com.aaa.gs.service;
import com.aaa.core.util.Result;
/**
* @ fileName:RemoteOrderService
* @ description:
* @ author:zhz
* @ createTime:2022/2/5 17:26
* @ version:1.0.0
*/
public interface RemoteOrderService {
/**
* 根据编号查询
* @param id
* @return
*/
Result getOrderById(Integer id);
}
package com.aaa.gs.service.impl;
import com.aaa.core.constant.ResultStatus;
import com.aaa.core.util.Result;
import com.aaa.gs.service.RemoteOrderService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
/**
* @ fileName:RemoteOrderServiceImpl
* @ description:
* @ author:zhz
* @ createTime:2022/2/5 17:27
* @ version:1.0.0
*/
@Service
public class RemoteOrderServiceImpl implements RemoteOrderService {
@Resource
private RestTemplate restTemplate;
@SentinelResource(value = "getOrderById",blockHandler
= "getOrderByIdBlockHandler",fallback = "getOrderByIdFallback")
public Result getOrderById(Integer id){
if(id==110){
throw new IllegalArgumentException("id非法!");
}
return restTemplate.getForObject("http://order-server/order/selectOne?id="+id, Result.class);
}
/**
* 服务限流操作
* @param id
* @param e
* @return
*/
public Result getOrderByIdBlockHandler(Integer id, BlockException e){
return new Result<>(ResultStatus.ERROR.getCode(),"服务限流操作",e.getMessage());
}
/**
* 用于在抛出异常的时候提供 fallback 处理逻辑
* @param id
* @param e
* @return
*/
public Result getOrderByIdFallback(Integer id,Throwable e){
return new Result<>(ResultStatus.ERROR.getCode(),"服务异常熔断",e.getMessage());
}
}
控制器编码及配置:
/**
* 通过主键查询单条数据
*
* @param productId 主键
* @return 单条数据
*/
@GetMapping("selectOne/{productId}/{orderId}")
public Result selectOne(@PathVariable Integer productId,@PathVariable Integer orderId) {
Product product = productService.getById(productId);
Result result = remoteOrderService.getOrderById(orderId);
product.setResult(result);
/* log.info(result+"...........result..............");
if(result.getCode()==200) {
Order order = JSON.parseObject(JSON.toJSONString(result.getData()), Order.class);
product.setOrder(order);
}*/
return success(product);
}
限流测试:
一秒多次刷新:http://localhost:5555/product/selectOne/1/1
添加限流策略:
一秒多次刷新:http://localhost:5555/product/selectOne/1/1
异常测试:
http://localhost:5555//product/selectOne/1/110
3.3.8 feign整合sentinel(项目实战)
以 market_server_openfeign_sentinel使用feign远程调用OrderServer为例,整合sentinel测试
远程服务代码及配置:
package com.aaa.sns.service;
import com.aaa.core.util.Result;
import com.aaa.sns.config.MyLBRule;
import com.aaa.sns.service.impl.OrderServiceFallbackImpl;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @ fileName:RemoteOrderService
* @ description:
* @ author:zhz
* @ createTime:2022/1/28 17:06
* @ version:1.0.0
*/
@FeignClient(value = "order-server",configuration = MyLBRule.class,fallback = OrderServiceFallbackImpl.class)
public interface RemoteOrderService {
/**
* 远程接口
* @param id
* @return
*/
@GetMapping("/order/selectOne")
Result selectById(@RequestParam("id") Integer id);
}
备份方法代码:
package com.aaa.sns.service.impl;
import com.aaa.core.constant.ResultStatus;
import com.aaa.core.util.Result;
import com.aaa.sns.service.RemoteOrderService;
import org.springframework.stereotype.Service;
/**
* @ fileName:OrderServiceFallbackImpl
* @ description:
* @ author:zhz
* @ createTime:2022/2/5 23:40
* @ version:1.0.0
*/
@Service
public class OrderServiceFallbackImpl implements RemoteOrderService {
@Override
public Result selectById(Integer id) {
return new Result(ResultStatus.ERROR.getCode(),"远程服务器异常,无法连接",null);
}
}
配置文件添加:
feign:
sentinel:
#开启openfeign的sentinel熔断
enabled: true
httpclient:
#feign远程连接超时时间 设置10秒 默认2秒
connection-timeout: 10000
服务层编码:
@Override
public Comment getById(Serializable id) {
Comment comment = this.baseMapper.selectById(id);
Result result = remoteOrderService.selectById(1);
/*Order order = JSON.parseObject(JSON.toJSONString(result.getData()), Order.class);
comment.setOrder(order);*/
comment.setResult(result);
return comment;
}
测试:
正常请求:
http://localhost:14444/comment/selectOne/1
关掉远程服务后,再次请求,观察效果
4,知识 点总结
5,本章面试题
- 点赞
- 收藏
- 关注作者
评论(0)