文章目录
- 服务熔断降级Sentinel
- 高并发请求模拟(这里我们使用contiperf来进行测试)
- 修改tomcat配置最大线程数
- 引入测试依赖
- 编写测试代码
- 服务雪崩
- 服务雪崩的容错方案(隔离、超时、限流、熔断、降级)
- 隔离机制:
- 超时机制:
- 限流机制:
- 熔断机制:
- 降级机制:
- 常见的容错组件
- Sentinel
- Sentinel特征
- Sentinel俩大部分
- 项目集成Sentinel与Sentinel控制面板
- 项目集成Sentinel
- 引入依赖
- 增加配置
- 安装Sentinel控制台
- 下载
- 启动
- 访问:地址[localhost:8080]()(默认用户/密码:sentinel/sentinel)
- Sentinel功能使用
- 流控规则限制
- 流控维度
- Sentinel规则种类
- Sentinel控制实现原理AOP(热插拔、责任链模式)
- 流控模式
- 关联流控模式
- 链路流控模式
- 案例
- 流控效果
- 熔断降级规则
- 慢调用比例
- 异常比例
- 异常数
- 热点规则(注意加上注解 @SentinelResource)
- 授权规则
- 系统规则
- 自定义Sentinel返回值
- @SentinelResource 使用了解
- Feign集成Sentinel走降级
- 在配置文件开启支持
- 编写降级容错类
- 为feign客户端配置降级容错类
SpringCloud、SpringCloudAlibaba、SpringBoot版本选择。为了避免各种千奇百怪的bug,我们还是采用官方推荐的毕业版本。
服务熔断降级Sentinel
高并发请求模拟(这里我们使用contiperf来进行测试)
修改tomcat配置最大线程数
server:
port: 8882
# 为了模拟高并发请求,将tomcat最大并发数修改为10
tomcat:
threads:
max: 10
引入测试依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
编写测试代码
@RequestMapping("/thread")
public String thread() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
return "Cloud2OrderApp thread";
}
public class ContiPerfTest {
@Rule
public ContiPerfRule i = new ContiPerfRule();
// invocations 并发数 threads 线程数
@Test
@PerfTest(invocations = 100, threads = 200)
// @Required(max = 100000, average = 250, totalTime = 100000)
public void test1() throws Exception {
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://localhost:8882/thread", String.class);
System.out.println(result);
}
}
这里同时我们在浏览器去请求该地址,响应会变得很慢
测试结论:此时会发现由于thread接口囤积大量请求,导致index方法访问出现问题,这就是服务雪崩的雏形。
服务雪崩
当一个接口高频访问耗费完资源会影响到其他接口的正常访问,这个场景扩展到不同的微服务会导致服务雪崩
由于服务与服务之间的依赖性,故障会传播,对整个微服务系统早长城灾难性的严重后果,这就是故障的雪崩效应。
雪崩发生的原因是多样的,可能是设计的容量不合理,或者是高并发下某一个方法相应很慢,或者某台机器的资源消耗殆尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错机制,保证在一个服务发生问题,不会影响其他服务的正常运作。
服务雪崩的容错方案(隔离、超时、限流、熔断、降级)
要防止雪崩扩散,就要做好服务的容错机制。
常见的容错思路:隔离、超时、限流、熔断、降级
隔离机制:
超时机制:
上有服务调用下游服务的时候,设置一个最大响应时间,如果超时,下游未做出响应则自动断开请求,释放线程。
限流机制:
限制系统输入输出流量以达到保护系统目的。为了保证系统稳定运行,一旦达到阈值,就需要限流采取措施来完成限流目的。
熔断机制:
当下游服务因为访问压力过大而响应变慢或者失败,上游服务为保证系统整体可用,暂时切断对下游服务调用。牺牲局部来保证整体可用性。
熔断一般有三种状态:
- 熔断关闭状态(Close):服务没有故障时,断路器的状态,对调用方不做任何限制
- 熔断开启状态(Open):后续对该服务接口调用不再经过网络,直接执行本地fallback方法
- 半熔断状态(Half-Open):尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,说明服务已经恢复,进入熔断关闭状态。如果成功率仍然很低,则重新进入熔断开启状态。
降级机制:
降级就是给服务提供一个最低的兜底方案,一旦服务无法正常调用,则使用该兜底方案。
常见的容错组件
Hystrix:Netflix开源的延迟容错库。隔离访问远程系统、服务,防止级联失败,提高系统可用性与容错性。
Resilience4J:轻量、简单、文档清晰、丰富的熔断工具。Hystrix官方推荐替代方案。支持SpringBoot1/2,支持Prometheus监控。
Sentinel:alibaba开源断路器实现。稳定。分布式系统的流量防卫兵。
Sentinel
一套用于服务容错的综合性解决方案。以流量为切入点,从流量控制、熔断降级、系统负载保护等多维度来保护系统稳定性。
Sentinel特征
- 丰富的应用场景:秒杀、消息削峰、集群流量控制、实时熔断下游不可用应用等
- 完备的实时监控:可以看到应用的单台机器秒级数据,500台以下规模的集群汇总运行情况。
- 广泛的开源生态:开箱即用的与其他开源库整合模块。引入依赖即可使用。
Sentinel俩大部分
- 核心库(Client):不依赖任何框架,可以运行于所有Java运行时环境。同时对Dubbo/SpringCloud等框架有较好支持。
- 控制台(Dashboard):基于SpringBoot开发,打包可以直接运行,无需tomcat容器。
项目集成Sentinel与Sentinel控制面板
项目集成Sentinel
引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
增加配置
spring:
cloud:
sentinel:
transport:
port: 8719 # 与控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:8080 # 指定控制台服务的地址
安装Sentinel控制台
下载
下载安装包:https://github.com/alibaba/Sentinel/releases/tag
sentinel-dashboard-2.0.0-alpha-preview.jar :https://github.com/alibaba/Sentinel/releases/tag/2.0.0-alpha
启动
# 直接使用java -jar命令启动项目(控制台本身是一个SpringBoot项目)
# -Dserver.port=8080 指定端口。
# -Dcsp.sentinel.dashboard.server=localhost:8080 指定控制台地址和端口,会自动向该地址发送心跳包。地址格式为:hostIp:port 配置成localhost:8080即监控自己
# -Dproject.name=sentinel-dashboard 指定Sentinel控制台程序显示名称
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
访问:地址localhost:8080(默认用户/密码:sentinel/sentinel)
如果出不来这个监控项,要多访问几次项目
Sentinel功能使用
流控规则限制
资源名称:唯一名称,默认是请求路径,可以自定义
针对来源:针对哪个微服务进行限流,默认default不区分,全部限制
是否集群:
阈值类型/单机阈值:
- QPS(每秒请求数):当调用你接口QPS达到阈值,进行限流
- 线程数:当调用接口线程数达到阈值,进行限流
多访问几次就会被流控
流控维度
Sentinel规则种类
流控规则、降级规则、热点规则、系统规则、授权规则
Sentinel控制实现原理AOP(热插拔、责任链模式)
流量控制的原理:监控应用流量的QPS或并发线程数等指标,当达到指定阈值时对流量进行控制,以避免被瞬时流量高峰冲垮,从而保证应用的高可用。
流控模式
- 直接(默认):当接口达到限流条件时,开启限流
- 关联:当关联的资源达到限流条件时,开启限流(适合做应用让步)
- 链路:当从某个接口过来的资源达到限流条件时,开启限流
关联流控模式
数据库读写操作相互影响。如果写操作过多,会造成读的性能下降。
或者比如下单接口后调用支付接口,如果下单访问过多占用支付接口性能。
链路流控模式
链路流控模式是指,当某个接口过来的资源达到限流条件时,开启限流。
案例
- 在配置文件中关闭sentinel的web-context-unify
spring:
cloud:
sentinel:
transport:
web-context-unify: false
流控效果
- 快速失败(默认):直接失败,抛出异常,不做任何额外处理,是最简单的结果。
- WarmUp:从开始阈值到最大QPS会有一个缓冲阶段,一开始的阈值是最大QPS的1/3,然后慢慢增长,知道最大阈值,适用于突然增大的流量转换为缓慢增长的场景
- 排队等待:让请求以均匀的速度通过,单机阈值以每秒通过数量,其余的排队等待,会让设置一个超时时间,当请求超过时间还未处理则会被丢弃。
熔断降级规则
- 慢调用比例:响应时间超长的请求数比例
- 异常比例:请求异常比例
- 异常数:请求异常数量
慢调用比例
下面配置表示在1秒内超过5个请求,且这些请求中的响应时间大于最大RT时间的10%就触发熔断。在接下来的10秒内都不调用真实方法处理。直接走降级方法。
异常比例
下面配置表示在1秒内有超过1个请求的10%就触发熔断,熔断时间间隔5秒。
异常数
下面配置表示1秒内5个请求中,有三次异常就触发熔断。熔断间隔时间5秒。
热点规则(注意加上注解 @SentinelResource)
热点是指经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的top n条数据,并推起进行访问控制。
如:统计一段时间内最常购买的商品ID并进行限制。对一段时间内频繁访问的用户ID进行限制。防止刷赞等。
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数
授权规则
很多时候,需要根据调用来源来判断你请求是否允许放行,这时候可以使用sentinel的来源访问控制(黑白名单)功能。来源访问控制根据资源的请求(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时可以通过,若配置黑名单,则请求来源位于黑名单时不通过,其余可以通过。
系统规则
- Load自适应(仅对Linux/Unix机器有效):系统load1作为启发指标,进行自适应系统保护,当系统load1超过设定的阈值,且系统当前并发线程数超过估算的系统容量才会触发系统保护(BBR阶段)。
系统容量由系统的maxQPS * minRT估算。设定参考值一般是CPU cores * 2.5. - CPU usage:当系统cpu使用率超过阈值就触发保护。(取值0.0-1.0)比较灵敏。
- 平均RT:当单台机器上所有入口流量的平均RT达到阈值就触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值就触发系统保护。
- 入口QPS:当单台机器上所有入口流量的QPS达到阈值就触发系统保护。
自定义Sentinel返回值
当前面设定规则没有满足,可以自定义异常返回。
- FlowException :限流异常
- DegradeException :降级异常
- ParamFlowException : 参数限流异常
- AuthorityException : 授权异常
- SystemBlockException : 系统负载异常
package com.hx.sentinel.error;
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 lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.CharSet;
import org.apache.http.Consts;
import org.apache.http.entity.ContentType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.Charset;
/**
* @author Huathy
* @date 2023-04-04 23:58
* @description
*/
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
Result result = null;
if (e instanceof FlowException) {
result = new Result(500101, "接口限流");
} else if (e instanceof DegradeException) {
result = new Result(500102, "接口降级");
} else if (e instanceof ParamFlowException) {
result = new Result(500101, "接口参数限流");
} else if (e instanceof AuthorityException) {
result = new Result(500101, "授权异常");
} else if (e instanceof SystemBlockException) {
result = new Result(500101, "系统负载异常");
} else {
result = new Result(500101, e.getMessage());
}
response.setCharacterEncoding(Consts.UTF_8.name());
response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
response.getWriter().write(JSON.toJSONString(result));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Result {
int code;
String msg;
}
}
@SentinelResource 使用了解
用于定义资源,并提供可选的异常处理和fallback配置项。
@RestController
public class AnnoController {
// 需求:当触发流控规则后,默认向抛出异常。
// 此时业务需要在抛出异常前,进行额外业务处理。或返回默认参数
@RequestMapping("/anno1")
@SentinelResource(value = "/anno1", blockHandler = "anno1BlockHandler", fallback = "anno1Fallback")
public Map<String, Object> anno1(String pm) {
if(pm == null || "".equals("")){
throw new RuntimeException("参数为空!");
}
Map<String, Object> res = new HashMap<>();
res.put("code", 200);
res.put("msg", "请求成功");
res.put("param", pm);
return res;
}
/**
* 当触发流控规则之后,立即触发该方法。
* 需要注意该handler方法的参数列表要与原方法一致,并在最后加上异常参数BlockException ex
*/
public Map<String, Object> anno1BlockHandler(String param, BlockException e) {
System.out.println("anno1 流控触发");
Map<String, Object> res = new HashMap<>();
res.put("code", 403);
res.put("msg", "触发流控默认返回方法");
res.put("param", param);
res.put("exception", e);
return res;
}
/**
* 当anno1方法执行报错的时候,立即触发该方法
* @param param
* @param e
* @return
*/
public Map<String, Object> anno1Fallback(String param, Throwable e) {
Map<String, Object> res = new HashMap<>();
res.put("code", 500);
res.put("msg", "处理出错,默认返回");
res.put("param", param);
res.put("exception", e);
return res;
}
}
Feign集成Sentinel走降级
在配置文件开启支持
feign:
sentinel:
enabled: true
编写降级容错类
@Component
public class ProductFallService implements ProductService {
@Override
public String index() {
return "熔断降级了!";
}
}
为feign客户端配置降级容错类
@FeignClient(value = "cloud2-product-server", fallback = ProductFallService.class)
public interface ProductService {
@GetMapping("/")
String index();
}