服务使用中的各种问题
服务雪崩
服务降级
服务熔断
服务限流
安装Sentinel
sentinel和项目必须在同一个ip下
sentinel组件由2部分构成
后台
前台8080
运行命令
前提
java8环境OK
8080端口不能被占用
命令
java -jar sentinel-dashboard-1.7.1.jar
访问sentinel管理界面
http://localhost:8080
登录账户密码均为 sentinel
初始化演示工程
启动Nacos8848成功
http://localhost:8848/nacos/#/login
创建Module cloudalibaba-sentinel-service8401
POM<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>wms</artifactId> <groupId>com.hk.wms</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>wms-sentinel-service8401</artifactId> <dependencies> <!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--监控--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
YMLserver: port: 8401 spring: application: name: wms-sentinal-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 192.168.1.131:8848 sentinel: transport: #配置Sentin dashboard地址 dashboard: localhost:8080 # 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口 port: 8719 service-url: nacos-user-service: http://wms-provider config-url: config-service: http://wms-config-3377 management: endpoints: web: exposure: include: '*'
主启动package com.hk.wms.wmssentinelservice8401; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class WmsSentinelService8401Application { public static void main(String[] args) { SpringApplication.run(WmsSentinelService8401Application.class, args); } }
业务类FlowLimitControllerpackage com.hk.wms.wmssentinelservice8401.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class FlowLimitController { @Value("${service-url.nacos-user-service}") String serverUrl ; @Value("${config-url.config-service}") String configUrl; @Autowired RestTemplate template; @GetMapping("/testA") public String testA() { return "----testA"; } @GetMapping("/testB") public String testB() { return "----testB"; } @GetMapping("/testC") public String testC() { return template.getForObject(serverUrl+"/provider/",String.class); } }
启动Sentinel8080
启动微服务8401
空空如也
Sentinel采用的懒加载说明
执行一次访问即可
http://localhost:8401/testA
http://localhost:8401/testB
sentinel8080正在监控微服务8401sentinel和项目必须在同一个ip下,sentinel才能监控微服务端口8401
流控规则
基本介绍
进一步解释说明
流控模式
直接(默认)
直接-> 快速失败
默认
配置及说明
测试
快速点击多次http://localhost:8401/testA
结果
思考
直接调用默认报错原因,技术方面OK,但是应该要有设置对应的报错信息和界面方便管理
关联
是什么
当关联的资源达到阈值时,就限流自己
当与A关联的资源B达到阈值后,就限流A自己
B惹事,A挂了
配置A
postman模拟并发密集访问testB
postman开启20个线程每隔0.3s请求/testB,导致A挂了
运行后发现testA挂了
流控效果
直接-> 快速失败(默认的流控处理)
直接失败,抛出异常
Blocked by Sentinel(flowing limit)
预热
说明
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会阈值
官网
默认ColdFactor为3,即请求QPS从threashold/3开始,经预热时长逐渐升到设定的QPS阈值
限流冷启动
WarmUp配置
多次点击http://localhost:8401/testB
应用场景
排队等待
匀速排队,严格控制请求通过的间隔时间
官网
测试
postman设置请求发送速率即可
降级规则
基本介绍
进一步说明
Sentinel的断路器是没有半开状态的
半开的状态,系统会自动检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用,具体可以参考Hystrix
RT
是什么
测试
代码@GetMapping("/testD") public String testD() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.info("testD 测试RT"); return "----testD"; }
配置
jmeter压测
结论
异常比例
是什么
配置
jmeter设置每秒10个请求即可
结论
异常数
是什么
异常数是按照分钟统计的
测试
代码@GetMapping("/testE") public String testE() { log.info("testE 测试异常数"); int age = 10 / 0; return "----testE 测试异常数"; }
配置
jmeter
热点key限流
基本介绍
是什么
@SentinelResource
代码@GetMapping("/testHotKey") @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey") public String testHotKey(@RequestParam(value = "p1",required = false)String p1, @RequestParam(value = "p2",required = false)String p2) { return "----testHotKey"; } public String deal_testHotKey(String p1, String p2, BlockException exception) { return "----deal_testHotKey, o(╥﹏╥)o"; // sentinel的默认提示都是: Blocked by Sentinel (flow limiting) }
com.alibaba.csp.sentinel.slots.block.BlockException
配置
1
@SentinelResource(value = "testHotKey")
异常会直接打印到前台用户界面,提示不太友好
2
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理
用了我们自己定义的
参数例外项
上诉案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
特例情况
普通
超过1秒钟一个后,达到阈值1后马上被限流
我们期望p1参数当它是某一个特殊值时,它的限流值和平时不一样
特例
假如当p1的值等于5时,它的QPS可以为200
配置
测试
http://localhost:8401/testHotKey?p1=1&p2=2
http://localhost:8401/testHotKey?p1=5&p2=2
当p1为5的时候,阈值QPS变为了200
前提条件
热点参数的注意点,参数必须为基本类型或者String
其他
添加一个java运行异常试试
系统规则
是什么
@SentinelResource
配置@SentinelResource后如果想使用blockHandler配置流控规则的资源名就必须使用@SentinelResource的value值
按资源名称限流+后续处理
启动Nacos成功
启动Sentinel成功
Module
cloudalibaba-sentinel-service8401
业务类RateLimitController@GetMapping("/testHotKey") @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey") public String testHotKey(@RequestParam(value = "p1",required = false)String p1, @RequestParam(value = "p2",required = false)String p2) { return "----testHotKey"; } public String deal_testHotKey(String p1, String p2, BlockException exception) { return "----deal_testHotKey, o(╥﹏╥)o"; // sentinel的默认提示都是: Blocked by Sentinel (flow limiting) }
配置流控规则
一秒点击一次,ok,疯狂点击,返回自己定义的限流处理信息,限流发生
额外问题
此时关闭服务8401
Sentinel控制台,流控规则消失了??
所以说规则是跟着微服务的生命周期的
上面兜底方案面临的问题
客户自定义限流处理逻辑
创建CustomerBlockHandler类用于自定义限流处理逻辑
自定义限流处理类
CustomerBlockHandlerpublic class CustomerBlockHandler { public static String customBlockHandler(BlockException e){ return "限流!!!!!"; } }
注意:customBlockHandler方法必须是静态的,否则无法被调用
RateLimitController@GetMapping("/customBlockHandler") @SentinelResource(value="customBlockHandler",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "customBlockHandler") public String customBlockHandler(){ return "测试成功"; }
启动微服务后先调用一次
Sentinel控制台配置
更多注解属性说明
多说一句
Sentinel主要有三个核心API
SPhU定义资源
Tracer定义统计
ContextUtil定义了上下文
服务熔断功能
Ribbon系列
启动nacos和sentinel
提供者9002/9001
POM ,YML,主启动,没有变化
业务类@GetMapping("/getStr/{id}") public String getStr(@PathVariable("id")int id){ return id+":"+serverPort; }
消费者8401
ApplicationContextConfigpackage com.hk.wms.wmscustom83.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
CircleBreakerController@GetMapping("/testF/{id}") @SentinelResource(value="testF",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "customBlockHandler1",fallback = "handlerFallBack") public String CircleBreakerController(@PathVariable("id")int id){ String str=template.getForObject(serverUrl+"/getStr/"+id,String.class); if(id<0){ throw new IllegalArgumentException("IllegalArgument ,非法参数异常..."); } if(str==null){ throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return str; }
修改后请重启微服务
热部署对java代码级生效及时 ,对@SentinelResource注解内属性,有时效果不好
fallback管运行异常,public String handlerFallBack(@PathVariable int id,Throwable e){ return "异常"; }
blockHandler管配置违规public static String customBlockHandler1(@PathVariable int id, BlockException e){ return "限流1!!!!!"; }
注意:fallback方法和blockHandler方法必须有和@SentinelResource注解的方法具有同样的形参,否则无法生效
同时配置fallback和blockHandler
异常忽略
Feign系列
修改8401模块
8401消费者调用提供者9002
Feign组件一般是消费端
POM<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
YMLserver: port: 8401 spring: application: name: wms-sentinal-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 192.168.1.131:8848 sentinel: transport: #配置Sentin dashboard地址 dashboard: localhost:8080 #8080端口将会监控8401 # 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口 port: 8719 datasource: ds1: nacos: server-addr: 192.168.1.131:8848 dataId: wms-sentinal-service groupId: DEFAULT_GROUP data-type: json rule-type: flow service-url: nacos-user-service: http://wms-provider config-url: config-service: http://wms-config-3377 management: endpoints: web: exposure: include: '*' #激活sentinel对feign的支持 feign: sentinel: enabled: true
业务类
带@FeignClient注解的业务接口
fallback = FeignFallbackService.classpackage com.hk.wms.wmssentinelservice8401.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(value="wms-provider",fallback = FeignFallbackService.class) public interface FeignService { @GetMapping(value="/getStr/{id}") public String getString(@PathVariable("id") int id); }
用于处理服务方的异常错误
package com.hk.wms.wmssentinelservice8401.service; import org.springframework.stereotype.Component; @Component public class FeignFallbackService implements FeignService{ @Override public String getString(int id) { return "服务降级返回"; } }
Controller@GetMapping("/testE/{id}") @SentinelResource(value="testE",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "customBlockHandler2",fallback = "handlerFallBack") public String getStrController(@PathVariable("id")int id){ String str=feignService.getString(id); if(id<0){ throw new IllegalArgumentException("IllegalArgument ,非法参数异常..."); } if(str==null){ throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return str; }
fallback管运行异常,
public String handlerFallBack(@PathVariable int id,Throwable e){ return "异常"; }
blockHandler管配置违规
public static String customBlockHandler2(@PathVariable int id, BlockException e){ return id+"限流1!!!!!"; }
注意:fallback方法和blockHandler方法必须有和@SentinelResource注解的方法具有同样的形参,否则无法生效
主启动
添加@EnableFeignClients启动Feign的功能package com.hk.wms.wmssentinelservice8401; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class WmsSentinelService8401Application { public static void main(String[] args) { SpringApplication.run(WmsSentinelService8401Application.class, args); } }
代码
package com.hk.wms.wmssentinelservice8401.controller; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.hk.wms.wmssentinelservice8401.myhandler.CustomerBlockHandler; import com.hk.wms.wmssentinelservice8401.service.FeignService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class FlowLimitController { @Resource FeignService feignService; @Value("${service-url.nacos-user-service}") String serverUrl ; @Value("${config-url.config-service}") String configUrl; @Autowired RestTemplate template; @GetMapping("/testA") @SentinelResource(value="testA",blockHandler = "deal_testA") public String testA(@RequestParam(value="p1",required = false)String p1) { System.out.println(Thread.currentThread().getName()+".................testA"); return "----testA"; } @GetMapping("/testB") @SentinelResource(value="testB") public String testB() { try { Thread.sleep(800L); } catch (InterruptedException e) { e.printStackTrace(); }finally { return "----testA"; } } @GetMapping("/testC") public String testC() { return template.getForObject(serverUrl+"/provider/",String.class); } @GetMapping("/testHotKey") @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey") public String testHotKey(@RequestParam(value = "p1",required = false)String p1, @RequestParam(value = "p2",required = false)String p2) { return "----testHotKey"; } @GetMapping("/customBlockHandler") @SentinelResource(value="customBlockHandler",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "customBlockHandler") public String customBlockHandler(){ return "测试成功"; } public String deal_testHotKey(String p1, String p2, BlockException exception) { return "----deal_testHotKey, o(╥﹏╥)o"; // sentinel的默认提示都是: Blocked by Sentinel (flow limiting) } public String deal_testA(String p1,BlockException e){ return "----testA+"; } @GetMapping("/testE/{id}") @SentinelResource(value="testE",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "customBlockHandler2",fallback = "handlerFallBack") public String getStrController(@PathVariable("id")int id){ String str=feignService.getString(id); if(id<0){ throw new IllegalArgumentException("IllegalArgument ,非法参数异常..."); } if(str==null){ throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return str; } @GetMapping("/testF/{id}") @SentinelResource(value="testF",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "customBlockHandler1",fallback = "handlerFallBack") public String CircleBreakerController(@PathVariable("id")int id){ String str=template.getForObject(serverUrl+"/getStr/"+id,String.class); if(id<0){ throw new IllegalArgumentException("IllegalArgument ,非法参数异常..."); } if(str==null){ throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return str; } public String handlerFallBack(@PathVariable int id,Throwable e){ return "异常"; } }
自定义 blockHandler管配置违规
package com.hk.wms.wmssentinelservice8401.myhandler; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.web.bind.annotation.PathVariable; /** * */ public class CustomerBlockHandler { public static String customBlockHandler(BlockException e){ return "限流!!!!!"; } public static String customBlockHandler1(@PathVariable int id, BlockException e){ return "限流1!!!!!"; } public static String customBlockHandler2(@PathVariable int id, BlockException e){ return id+"限流1!!!!!"; } }
熔断框架比较
Sentinel规则持久化
一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
步骤
修改cloudalibaba-sentinel-service8401
POM
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
YML
server: port: 8401 spring: application: name: wms-sentinal-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 192.168.1.131:8848 sentinel: transport: #配置Sentin dashboard地址 dashboard: localhost:8080 #8080端口将会监控8401 # 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口 port: 8719 #datasource是sentinel的一个属性 datasource: ds1: nacos: server-addr: 192.168.1.131:8848 dataId: wms-sentinal-service groupId: DEFAULT_GROUP data-type: json rule-type: flow service-url: nacos-user-service: http://wms-provider config-url: config-service: http://wms-config-3377 #暴露所有接口 management: endpoints: web: exposure: include: '*' #激活sentinel对feign的支持 feign: sentinel: enabled: true
#datasource是sentinel的一个属性 datasource: ds1: nacos: server-addr: 192.168.1.131:8848 dataId: wms-sentinal-service groupId: DEFAULT_GROUP data-type: json rule-type: flow
注意:datasource是sentinel的一个属性,properties写法:spring.cloud.sentinel.datasource
添加Nacos业务规则配置
[ { "resource": "testF", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
内容解析
注意:resource的写法有两种,一种是按照路径的写法,就是@requestMapping的value,一种是写 @SentinelResource 的value这一步的作用是每次消费者微服务启动时在nacos中定义sentinel的流控规则,从而做到持久化的效果
启动8401后刷新sentinel发现业务规则有了
停止8401再看sentinel
发现流控规则没有了
重新启动8401再看sentinel
咋一看没有,但是你调用 /rateLimit/byUrl后它就会出现