目录
高并发带来的问题
结论:
服务器雪崩效应
常见容错方案
隔离机制:
超时机制
限流机制
熔断机制:
降级机制
常见的容错组件
Sentinel入门
什么是Sentinel
订单微服务集成Sentinel
安装Sentinel控制台
实现⼀个接口的限流
Sentinel容错的维度
Sentinel规则种类
编辑
Sentinel规则-流控
流控规则
QPS流控
线程数流控
流控模式
直接流控模式
关联流控模式
链路流控模式
流控效果
Sentinel规则-降级
慢调用比例案例
异常比例案例
异常数案例
Sentinel规则-热点
Sentinel规则-授权
Sentinel规则-系统规则
Sentinel ⾃定义异常返回
@SentinelResource的使用
Feign整合Sentinel
/**
* Created by mxin5
* 验证高并发
*/
@RestController
public class SentinelController {
@RequestMapping("/sentinel1")
public String sentinel1(){
//模拟一次网络延时
try {
//进行休眠
TimeUnit.SECONDS.sleep(3);
System.out.println("sentinel1开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "sentinel1";
}
@RequestMapping("/sentinel2")
public String sentinel2(){
System.out.println("sentinel2开始执行");
return "测试高并发下的问题";
}
@RequestMapping("/sentinel3")
public String sentinel3(){
return "sentinel3";
}
}
server:
port: 8091 #商品订单的端口号
tomcat:
threads:
max: 10 #tomcat的最大并发值10,正常是200多个,测试高并发,就需要sentinel进行限流
第⼆步:添加线程组
第三步:配置线程并发数
第六步:访问 http://localhost:8091/sentinel2 观察结果,会出现转圈等待的效果。
此时会发现 , 由于 sentinel1 能够承受的并发数为10个,而jmeter压测的时候50个线程同事访问sentinel1导致囤积了⼤量请求 , 从而在访问sentinel2的时候,没有线程进行处理 sentinel2 的访问,这就 是服务雪崩的雏形。
情景2:某个时刻,服务A挂了,服务B和服务C依然在调⽤服务A
情景3:由于服务A挂了,导致服务C和服务B⽆法得到服务A的响应,这时候服务C和服务B由于⼤量线程积压,最终导致服务C和服务B挂掉.
情景4: 相同道理,由于服务之间有关联,所以会导致整个调⽤链上的所有服务都挂掉.
服务器的雪崩效应其实就是由于某个微⼩的服务挂了,导致整⼀⼤⽚的服务都不可⽤.类似⽣活中的雪崩效应,由于落下的最后⼀⽚雪花引发了雪崩的情况. 雪崩发⽣的原因多种多样,有不合理的容量设计,或者是⾼并发下某⼀个⽅法响应变慢,亦或某 台机器的资源耗尽。我们⽆法完全杜绝雪崩源头的发⽣,只有做好⾜够的容错,保证在⼀个服务发⽣问题,不会影响到其它服务的正常运⾏。
HystrixHystrix 是由 Netflflix 开源的⼀个延迟和容错库,⽤于隔离访问远程系统、服务或者第三⽅库,防⽌级联失败,从⽽提升系统的可⽤性与容错性。Resilience4JResilicence4J ⼀款⾮常轻量、简单,并且⽂档⾮常清晰、丰富的熔断⼯具,这也是 Hystrix 官⽅推荐的替代产品。不仅如此, Resilicence4j 还原⽣⽀持 Spring Boot 1.x/2.x ,⽽且监控也⽀持和prometheus 等多款主流产品进⾏整合。SentinelSentinel 是阿⾥巴巴开源的⼀款断路器实现,本身在阿⾥内部已经被⼤规模采⽤,⾮常稳定。
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
# 直接使⽤ jar 命令启动项⽬ ( 控制台本身是⼀个 SpringBoot 项⽬ )java -Dserver .port = 8080 -Dcsp .sentinel.dashboard .server = localhost:8080 -Dproject .name = sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
3. 修改shop-order-server项⽬中的配置⽂件application.yml,新增如下配置:
spring: cloud: sentinel: transport: port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可 dashboard: localhost:1111 # 指定控制台服务的地址
第⼀步: 簇点链路--->流控
第⼆步: 在单机阈值填写⼀个数值,表示每秒上限的请求数
Sentinel主要提供了这五种的流量控制
3. 访问 http://localhost:8091/sentinel2 会发现已经被限流
点击上⾯设置流控规则的编辑按钮,然后在编辑⻚⾯点击⾼级选项,会看到有流控模式⼀栏。
sentinel 共有三种流控模式,分别是:直接(默认):接⼝达到限流条件时,开启限流关联:当关联的资源达到限流条件时,开启限流 [ 适合做应⽤让步 ]链路:当从某个接⼝过来的资源达到限流条件时,开启限流
@RequestMapping("/sentinel3")
public String sentinel3(){
return "sentinel3";
}
3. 通过postman软件向/sentinel2连续发送请求,注意QPS⼀定要⼤于3
4. 访问/sentinel3,会发现已经被限流
spring :cloud :sentinel :web-context-unify : false
@Service
@Slf4j
public class TraceServiceImpl {
@SentinelResource(value = "tranceService")
public void tranceService(){
log.info("调⽤tranceService⽅法");
}
}
@RestController
public class TraceController {
@Autowired
private TraceServiceImpl traceService;
@RequestMapping("/trace1")
public String trace1(){
traceService.tranceService();
return "trace1";
}
@RequestMapping("/trace2")
public String trace2(){
traceService.tranceService();
return "trace2";
}
}
5. 分别通过 /trace1 和 /trace2 访问, 发现/trace1没问题, /trace2的被限流了
@RestController
@Slf4j
public class FallBackController {
@RequestMapping("/fallBack1")
public String fallBack1(){
try {
log.info("fallBack1执行业务逻辑");
//模拟业务耗时
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "fallBack1";
}
}
⽐如: 最⼤RT=900,⽐例阈值=0.1,熔断时⻓=10,最⼩请求数=10
情况 1: 1 秒内的有 20 个请求,只有 10 个请求响应时间 >900ms, 那慢调⽤⽐例 =0.5 ,这种情况就会触发熔断情况 2: 1 秒内的有 20 个请求,只有 1 个请求响应时间 >900ms, 那慢调⽤⽐例 =0.05 ,这种情况不会触发熔断情况 3: 1 秒内的有 8 个请求,只有 6 个请求响应时间 >900ms, 那慢调⽤⽐例 =0.75 ,这种情况不会触发熔断,因为最⼩请求数这个条件没有满⾜ .
int i=0;
@RequestMapping("/fallBack2")
public String fallBack2(){
log.info("fallBack2执行业务逻辑");
//模拟出现异常,异常比例为33%
if(++i%3==0){
throw new RuntimeException();
}
return "fallBack2";
}
@RequestMapping("/fallBack3")
public String fallBack3(String name){
log.info("fallBack3执行业务逻辑");
//模拟出现异常,异常比例为33%
if("wolfcode".equals(name)){
throw new RuntimeException();
}
return "fallBack3";
}
@RestController
@Slf4j
public class HotSpotController {
@RequestMapping("/hotSpot1")
@SentinelResource(value = "hotSpot1")
public String hotSpot1(Long productId){
log.info("访问编号为:{}的商品",productId);
return "hotSpot1";
}
}
3. 在热点规则中编辑规则,在编辑之前⼀定要先访问⼀下/hotSpot1,不然参数规则⽆法新增.
5. 点击保存,可以看到已经新增了参数规则
@Component
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
/**
* 定义从请求的什么地方获取来源信息
* 比如我们可以要求所有的客户端需要在请求头中携带来源信息
*/
String serviceName = request.getParameter("serviceName");
return serviceName;
}
}
@RestController
@Slf4j
public class AuthController {
@RequestMapping("/auth1")
public String auth1(String serviceName){
log.info("应用:{},访问接口",serviceName);
return "auth1";
}
}
@Component
public class ExceptionHandlerPage implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setContentType("application/json;charset=utf-8");
ResultData data = null;
if (e instanceof FlowException) {
data = new ResultData(-1, "接口被限流了");
} else if (e instanceof DegradeException) {
data = new ResultData(-2, "接口被降级了");
}else if (e instanceof ParamFlowException) {
data = new ResultData(-3, "参数限流异常");
}else if (e instanceof AuthorityException) {
data = new ResultData(-4, "授权异常");
}else if (e instanceof SystemBlockException) {
data = new ResultData(-5, "接口被降级了...");
}
response.getWriter().write(JSON.toJSONString(data));
}
}
@Data
@AllArgsConstructor//全参构造
@NoArgsConstructor//无参构造
class ResultData {
private int code;
private String message;
}
@RestController
@Slf4j
public class AnnoController {
@RequestMapping("/anno1")
@SentinelResource(value = "anno1",
blockHandler="anno1BlockHandler",
blockHandlerClass = AnnoControllerBlockHandlerClass.class,
fallback = "anno1Fallback"
)
public String anno1(String name){
if("wolfcode".equals(name)){
throw new RuntimeException();
}
return "anno1";
}
public String anno1BlockHandler(String name,BlockException ex) throws BlockException {
log.error("{}", ex);
return "接口被限流或者降级了";
}
//Throwable时进入的方法
public String anno1Fallback(String name,Throwable throwable) {
log.error("{}", throwable);
return "接口发生异常了";
}
}
1. 在shop-order-server项⽬的配置⽂件中开启feign对Sentinel的⽀持
feign:
sentinel:
enabled: true
@Component
public class ProductFeignFallBack implements ProductFeignApi {
@Override
public Product findByPid(Long pid) {
Product product = new Product();
product.setPid(-1L);
product.setPname("服务调用失败返回的兜底数据");
product.setPprice(0.0);
return product;
}
}
@FeignClient(name = "product-service", fallback = ProductFeignFallBack.class)
public interface ProductFeignApi {
//跟product-sever中的controller的接口一致
@RequestMapping("/product/{pid}")
public Product findByPid(@PathVariable("pid") Long pid);
}