服务保护的基本概念
服务限流/熔断
服务限流目的是为了更好的保护我们的服务,在高并发的情况下,如果客户端请求的数量达到一定极限(后台可以配置阈值),请求的数量超出了设置的阈值,开启自我的保护,直接调用我们的服务降级的方法,不会执行业务逻辑操作,直接走本地falback的方法,返回一个友好的提示。
服务降级
在高并发的情况下, 防止用户一直等待,采用限流/熔断方法,使用服务降级的方式返回一个友好的提示给客户端,不会执行业务逻辑请求,直接走本地的falback的方法。
提示语:当前排队人数过多,稍后重试~
服务的雪崩效应
默认的情况下,Tomcat或者是Jetty服务器只有一个线程池去处理客户端的请求,
这样的话就是在高并发的情况下,如果客户端所有的请求都堆积到同一个服务接口上,
那么就会产生tomcat服务器所有的线程都在处理该接口,可能会导致其他的接口无法访问。
假设我们的tomcat线程最大的线程数量是为20,这时候客户端如果同时发送100个请求会导致有80个请求暂时无法访问,就会转圈。
服务的隔离的机制
服务的隔离机制分为:信号量和线程池隔离模式
服务的线程池隔离机制:每个服务接口都有自己独立的线程池,互不影响,缺点就是占用cpu资源非常大。
服务的信号量隔离机制:最多只有一定的阈值线程数处理我们的请求,超过该阈值会拒绝请求。
Sentinel 与hytrix区别
前哨以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。
前哨具有以下特征:
- 丰富的应用场景:前哨兵承接了阿里巴巴近10年的双十一大促流的核心场景,例如秒杀(即突然流量控制在系统容量可以承受的范围),消息削峰填谷,传递流量控制,实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel同时提供实时的监控功能。您可以在控制台中看到接收应用的单台机器秒级数据,甚至500台以下规模的整合的汇总运行情况。
广泛的开源生态:Sentinel提供开箱即用的与其他开源框架/库的集成模块,例如与Spring Cloud,Dubbo,gRPC的整合。您只需要另外的依赖并进行简单的配置即可快速地接入Sentinel。 - 完善的SPI扩展点:Sentinel提供简单易用,完善的SPI扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理,适应动态数据源等。
SpringBoot项目整合Sentinel
Sentinel 控制台 下载地址
https://github.com/alibaba/Sentinel/releases
启动命令 其中 -Dserver.port=8xxx 用于指定 Sentinel 控制台端口为 8xxx。
java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar E:\xxx\sentinel\sentinel-dashboard-1.8.0.jar
默认用户名和密码都是 sentinel
Maven依赖
<!-- alibaba sentinel 限流熔断-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- alibaba sentinel 整合-gateway-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
客户端接入控制台 Maven依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>x.y.z</version>
</dependency>
配置启动参数
启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port
指定控制台地址和端口。若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx 指定客户端监控 API
的端口(默认是 8719)。从 1.6.3 版本开始,控制台支持网关流控规则管理。您需要在接入端添加 -Dcsp.sentinel.app.type=1
启动参数以将您的服务标记为 API Gateway,在接入控制台时您的服务会自动注册为网关类型,然后您即可在控制台配置网关规则和 API
分组。除了修改 JVM 参数,也可以通过配置文件取得同样的效果。更详细的信息可以参考 启动配置项。
gateway项目整合Sentinel的yml配置
spring:
cloud:
# sentinel 配置 注册到控制台地址
sentinel:
transport:
dashboard: 127.0.0.1:8718
# 默认false 开启true
eager: true
###配置那些接口需要限流
gateway:
seckill:
intercept:
url: /tst-seckill/spike
SeckillFilter
/**
* @description: TODO 秒杀服务拦截
* @author: wang
* @date: 2020/10/12 12:08
* @version: v1.0
*/
@Component
public class SeckillFilter implements GlobalFilter, Ordered {
@Autowired
private SeckillService seckillService;
@Value("${gateway.seckill.intercept.url}")
private List<String> gatewaySeckillInterceptUrl;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
ServerHttpRequest request = exchange.getRequest();
HttpHeaders header = response.getHeaders();
header.add("Content-Type", "application/json; charset=UTF-8");
RequestPath path = request.getPath();
// 排除请求 判断地址是否在限流名单里
boolean resultExist = existSeckillUrl(path.value());
if (!resultExist) {
// 直接放行
return chain.filter(exchange);
}
try {
String userPhone = getParam(request.getURI().getQuery(), "userPhone");
String seckillId = getParam(request.getURI().getQuery(), "seckillId");
seckillService.seckill(userPhone, seckillId);
return chain.filter(exchange);
} catch (Exception e) {
JSONObject jsonObject = setResultErrorMsg("当前访问用户过多,请稍后重试");
DataBuffer buffer = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
return response.writeWith(Mono.just(buffer));
}
}
/**
* 值越小 排序靠前
* @return
*/
@Override
public int getOrder() {
return 0;
}
/**
* 返回提示
* @param msg
* @return
*/
private JSONObject setResultErrorMsg(String msg) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "500");
jsonObject.put("msg", msg);
return jsonObject;
}
/**
*
* @param url
* @param name
* @return
*/
public static String getParam(String url, String name) {
String params = url.substring(url.indexOf("?") + 1, url.length());
Map<String, String> split = Splitter.on("&").withKeyValueSeparator("=").split(params);
return split.get(name);
}
public boolean existSeckillUrl(String url) {
return gatewaySeckillInterceptUrl.contains(url);
}
SeckillService
/**
* @description: TODO 秒杀服务 限流服务
* @author: wang
* @date: 2020/10/12 12:06
* @version: v1.0
*/
@Component
public class SeckillService {
/**
* @SentinelResource-value 资源名称-跟控制台一样
* @param userPhone
* @param seckillId
* @return
* @throws Exception
*/
@SentinelResource(value = "seckill", blockHandler = "getSeckillQpsException")
public String seckill(String userPhone, String seckillId) throws Exception {
return "success";
}
/**
* 被限流返回的提示
* @SentinelResource-blockHandler 限流或熔断出现异常执行的方法
* @param userPhone
* @param seckillId
* @param e
* @return
* @throws Exception
*/
public String getSeckillQpsException(String userPhone, String seckillId, BlockException e) throws Exception {
throw new RuntimeException();
}
}
Sentinel持久化
默认的情况下Sentinel的规则是存放在内存中,如果Sentinel客户端重启后,Sentinel数据规则可能会丢失。
解决方案:
Sentinel持久化机制支持四种持久化的机制。
- 本地文件
- 携程阿波罗
- Nacos
- Zookeeper
Maven依赖
<!--sentinel 整合nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
在Nacos创建规则yml文件 josn格式
[
{
"resource": "/ getOrderSentinel",
"limitApp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
resource:资源名,即限流规则的作用对象
limitApp:流控针对的调用来源,若为 default 则不区分调用来源
grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
count:限流阈值
strategy:调用关系限流策略
controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
clusterMode:是否为集群模式
yaml配置
sentinel:
transport:
dashboard: 127.0.0.1:8718
eager: true
datasource:
ds:
nacos:
### nacos连接地址
server-addr: localhost:8848
## nacos连接的分组
group-id: DEFAULT_GROUP
###路由存储规则
rule-type: flow
### 读取配置文件的 data-id
data-id: xxx-order-sentinel
### 读取培训文件类型为json
data-type: json
–整理自 http://www.mayikt.com/ 仅供自己学习记录