https://github.com/alibaba/Sentinel
https://github.com/alibaba/Sentinel/releases
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel
服务使用中的各种问题:
核心库(Java客户端) 不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo/Spring Cloud等框架也有较好的支持
控制台(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器
java -jar sentinel-dashboard-1.7.1.jar 前提是(java8环境OK 8080端口不能被占用)
http://localhost:8080/#/login
登录账号密码均为sentinel
pom文件
<dependencies>
<!-- nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos持久化-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- SpringCloud alilibaba sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 引入自己定义的api通用包-->
<dependency>
<groupId>com.jd.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- boot web actuator-->
<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>
<!-- devtools-->
<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>
yml文件
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
主启动
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
业务类 FlowLimitController
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA(){
return "-------testA";
}
@GetMapping("/testB")
public String testB(){
return "------testB";
}
}
java -jar sentinel-dashboard-1.7.1.jar
需要先执行一次访问
直接-> 快速失败
配置及说明
表示1秒钟内查询1次就是OK, 若超过次数1,就直接-快速失败,报默认错误
测试
快速点击访问 http://localhost:8401/testA
结果
是什么
配置A
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名
postman模拟并发密集访问testB
(1) 访问testB成功
(3) 将访问地址添加进新新线路组
大批量线程高并发访问B,导致testA挂了
说明
公式: 阈值除以coldFactor(默认值为3),即请求 QPS从threshold/3开始,经过预热时长逐渐升至设定的QPS阈值
warmUp配置
多次点击 http://localhost:8401/testB 刚开始不行,后续慢慢OK
应用场景
如 秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
降级策略
我们通常用以下几种方式来衡量资源是否处于稳定的状态:
平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是4900 ms,超出此阈值的都会算作 4900ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
sentinel断路器是没有半开状态的
(1)代码
@GetMapping("/testD")
public String testD(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 测试RT");
return "------testD";
}
(2)配置
按照上述配置
永远一秒钟打进来10个线程(大于5个了) 调用testD, 我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK
(1)代码
@GetMapping("/testD")
public String testD(){
log.info("testD 异常比例");
int age = 10 / 0;
return "------testD";
}
(2)配置
(4)结论
按照上述配置,单独访问一次,必然来一次报错一次(int age=10/0),调一次错一次;
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。断路器开启(保险丝跳闸),微服务
不可用了,不再报错error而是直接服务降级了
异常数是按照分钟统计的
(1) 代码
@GetMapping("/testE")
public String testE(){
log.info("testE 测试异常数");
int age = 10 / 0;
return "------testE 测试异常数";
}
(2) 配置
http://localhost:8401/testE
第一次访问绝对报错,因为除数不能为零,我们看到error窗口,但是达到5次报错后,进入熔断后降级。
@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";
}
访问 http://localhost:8401/testHotKey?p1=a
http://localhost:8401/testHotKey?p1=a&p2=3
@SentinelResource(value=“testHotKey”,blockHandler = “deal_testHotKey”)
方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理
特例情况:
期望p1参数当它是某个特殊值时,它的限流值和平时不一样
例如:当p1的值等于5时,它的阈值可达到300
配置如下:
测试:
http://localhost:8401/testHotKey?p1=5
当p1=5的时候,阈值变为300
如果加个异常代码会怎么样
@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){
int age = 10 / 0;
return "-----testHotKey";
}
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理
但是 int age = 10/0,这个是 java运行时报出的异常,@SentinelResource不管
pom添加
<!-- 引入自己定义的api通用包-->
<dependency>
<groupId>com.jd.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
新增业务类 RateLimitController
@RestController
public class RateLimitController {
@GetMapping("/byResource")
@SentinelResource(value="byResource",blockHandler = "handleException")
public CommonResult byResource(){
return new CommonResult(200,"",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception){
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
}
1秒钟点击一下,ok,超过上述疯狂点击,返回自定义的限流处理信息,限流发生
通过访问的URL 来限流,会返回sentinel自带默认的限流处理信息
业务类RateLimitController
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value="byUrl")
public CommonResult byUrl(){
return new CommonResult(200, "按url限流测试OK",new Payment(2020L,"serial002"));
}
配置
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class,blockHandler = "handlerException2")
public CommonResult customerBlockHandler(){
return new CommonResult(200, "按客户自定义",new Payment(2020L,"serial003"));
}
sentinel主要有三个核心 Api,SphU定义资源, Tracer定义统计, ContextUtil定义了上下文
sentinel整合ribbon+openFeign+fallback
PaymentController类
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static {
hashMap.put(1L,new Payment(1L,"28a2181"));
hashMap.put(2L,new Payment(2L,"bba2182"));
hashMap.put(3L,new Payment(3L,"6ua2183"));
}
@GetMapping(value="/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
Payment payment = hashMap.get(id);
CommonResult result = new CommonResult(200, "from mysql, serverPort:" + serverPort, payment);
return result;
}
}
yml文件
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置sentinel dashboard
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
config
package com.jd.springcloud.alibaba.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();
}
}
controller
package com.jd.springcloud.alibaba.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.jd.springcloud.alibaba.entities.CommonResult;
import com.jd.springcloud.alibaba.entities.Payment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class CircleBreakerController {
public static final String SERVICE_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value="fallback")
public CommonResult<Payment> fallback(@PathVariable Long id){
CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException, 非法参数异常");
} else if(result.getData() == null){
throw new NullPointerException("NullPointerException, 该ID 没有对应记录,空指针异常");
}
return result;
}
}
只配置fallback
CircleBreakerController添加如下代码
@RestController
public class CircleBreakerController {
public static final String SERVICE_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value="fallback",fallback = "handlerFallback") //fallback 只负责业务异常
public CommonResult<Payment> fallback(@PathVariable Long id){
CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException, 非法参数异常");
} else if(result.getData() == null){
throw new NullPointerException("NullPointerException, 该ID 没有对应记录,空指针异常");
}
return result;
}
//本例是fallback
public CommonResult handlerFallback(@PathVariable Long id, Throwable e){
Payment payment = new Payment(id,"null");
return new CommonResult(444,"兜底异常handlerFallback,exception内容"+e.getMessage(),payment);
}
}
只配置 blockHandler
package com.jd.springcloud.alibaba.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.jd.springcloud.alibaba.entities.CommonResult;
import com.jd.springcloud.alibaba.entities.Payment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class CircleBreakerController {
public static final String SERVICE_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id){
CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException, 非法参数异常");
} else if(result.getData() == null){
throw new NullPointerException("NullPointerException, 该ID 没有对应记录,空指针异常");
}
return result;
}
//本例是blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException){
Payment payment = new Payment(id,"null");
return new CommonResult(445, "blockHandler-sentinel限流,无此流水"+blockException.getMessage());
}
}
package com.jd.springcloud.alibaba.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.jd.springcloud.alibaba.entities.CommonResult;
import com.jd.springcloud.alibaba.entities.Payment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class CircleBreakerController {
public static final String SERVICE_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value="fallback",fallback = "handlerFallback") //fallback 只负责业务异常
//@SentinelResource(value = "fallback",blockHandler = "blockHandler")
@SentinelResource(value="fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id){
CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException, 非法参数异常");
} else if(result.getData() == null){
throw new NullPointerException("NullPointerException, 该ID 没有对应记录,空指针异常");
}
return result;
}
//本例是fallback
public CommonResult handlerFallback(@PathVariable Long id, Throwable e){
Payment payment = new Payment(id,"null");
return new CommonResult(444,"兜底异常handlerFallback,exception内容"+e.getMessage(),payment);
}
//本例是blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException){
Payment payment = new Payment(id,"null");
return new CommonResult(445, "blockHandler-sentinel限流,无此流水"+blockException.getMessage());
}
}
若blockHandler和fallback都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑
修改cloudalibaba-consumer-nacos-order84
pom文件添加 feign依赖
<!-- SpringCloud openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
yml 文件
# 激活sentinel 对feign的支持
feign:
sentinel:
enabled: true
主启动类
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
业务类
带@FeignClient注解的业务接口
@FeignClient(value="nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value="/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444, "服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
}
}
controller 添加
//==============OpenFeign
@Resource
private PaymentService paymentService;
@GetMapping(value="/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
return paymentService.paymentSQL(id);
}
访问
http://localhost:84/consumer/paymentSQL/1
测试84 调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级
一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
pom添加持久化依赖
<!-- nacos持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
yml文件添加Nacos数据源配置
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
添加Nacos业务规则配置
http://localhost:8401/rateLimit/byUrl
乍一看还是没有,稍等一会 多次调用 http://localhost:8401/rateLimit/byUrl
重新配置出现了,持久化验证通过