1. Sentinel是什么:
官网:https://github.com/alibaba/sentinel
中文版:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
1.1 其实就是代替Hystrix的功能,解决:
- 服务熔断
- 服务降级
- 服务限流
- 服务雪崩
1.2 分为两部分:
2. 下载安装:
2.1 jar包下载
- 官网github下载jar包;
- 在下载目录下启动jar包:java -jar jar包名称;
- localhost:8080登录,用户名密码都是sentinel
2.2 docker下载
#拉取sentinel镜像
docker pull bladex/sentinel-dashboard
#运行sentinel(docker里的sentinel是8858端口)
docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard
#把nacos和mysql也启动起来
访问:账密都是sentinel
3. 初始化演示工程:
- 新建模块cloudalibaba-sentinel-service8401
- 添加pom:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 添加yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinal-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址(改成自己的服务器ip地址,本地用localhost)
server-addr: 10.211.55.26:8848
sentinel:
transport:
#配置Sentin dashboard地址(改成自己的服务器ip地址,本地用localhost)
dashboard: 10.211.55.26:8858
# 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
- 添加controller
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "----testA";
}
@GetMapping("/testB")
public String testB() {
return "----testB";
}
}
- 测试,启动8401,然后刷新sentinel后台页面,可以看到什么都没有。因为sentinel采用懒加载策略,所以需要调用服务后才在后台显示。
- 先访问接口,然后刷新sentinel后台页面:
4. 流控规则:
两种方式:
- 直接在流控规则中新建:
- 在簇点规则中新建:
流量控制:
4.1 三种流控模式:
4.1.1 直接(默认):
超过一秒一次(或者设置线程数阙值,访问该api的线程数超过1秒一个),就会被限流,报错系统默认的错误信息:
阕值类型
QPS是直接挡在外面,而线程数是有多少个线程在处理,放进来后,有线程是空闲状态就对请求进行处理,都没空闲,就限流(关门打狗)。
4.1.2 关联:
当关联的资源达到阈值时,就限流自己。
- 当与A关联的资源B达到阈值后,就限流A自己。
- 此时不管调用多少次A都不会限流,而此时超过1秒调用1次B,则会限流A。
- 设置postman频繁访问testB:
- 测试A可以看到A又挂了:
- 等线程B访问结束后,A恢复正常。
4.1.3 链路:
多个请求调用了同一个微服务。
4.2 三种流控效果:
4.2.1 快速失败(默认):
直接失败,抛出异常,即上面的流控模式的测试的处理方式。
4.2.2 预热:
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值。
解释下:阈值设置为10,预热时长设置为10s,那么在前10秒钟,阈值其实时10/3,每秒限制3个qps,当达到10s后,qps才会提升到10个。
源码:
作用:秒杀系统在开启的瞬间,会有很多流量上来,很可能把系统打死,预热方式就是为了保护系统,把流量慢慢的放进来,慢慢的把阙值增长到设置的阙值。
4.2.3 排队等待:
作用:用于处理间隔性突发的流量,例如消息队列。例如这样的场景:在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是第一秒直接拒绝多余的请求。测试:postman模拟10个请求,可以看到请求每秒一个处理了:
5. 降级规则:
注意sentinel断路器是没有半开状态的:
半开状态:半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,由异常则继续打开断路器不可用。
5.1 平均响应时间:
测试:
这个200ms是平均响应时间,而不是某一次的响应时间。
后续停止jmeter了,1s后,访问恢复正常。
5.2 异常比例:
测试:
上述配置意思是:每秒大于等于5个请求,并且每秒内的请求中百分之20都失败了,那么就进入服务降级。请求恢复后,3秒钟之后,退出服务降级继续处理请求。
5.3 异常数:
测试:
一分钟内,如果访问处理出现异常的次数超过5次,熔断降级,进入时间窗口期不处理请求,61秒后退出时间窗口期继续处理请求。(时间窗口必须大于等于60秒,防止再次熔断降级)
6. 热点key限流:
解释:比如某个商品被经常访问:localhost:80/get?pid=2;那么会对这条访问进行限流,但是不会对localhost:80/get?pid=3进行限流。
6.1 复习兜底方法:
即之前学的hystrix的兜底方法,如果发生了熔断,自定义一个兜底的方法,给用户一个友好提示。
6.2 限流配置:
- 在FlowLimitController中添加:主要是
@SentinelResource
注解,跟原来的HystrixCommand一样
@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)
}
- 配置:testHotKey这个方法的第一个参数,限制同一个值每秒访问一次,超过就进行限流,调用兜底方法
- 访问热点key即第一个参数,每秒访问一次,正常返回
- 如果不设置blockHandler兜底方法,多次请求后,会报出错误页面,不太友好
- 注意:如果程序中有运行异常,并不会进入兜底方法,@SentinelResource+blockHandler注解管的只是热点key的配置(fallback会处理业务异常)。
- 设置兜底方法:如果每秒的访问请求带有索引为0的参数的数量超过1,进入统计窗口期,然后调用兜底方法
- 1秒后退出统计窗口期,继续处理请求
6.3 参数例外项:
意思就是:比如当我们的第一个参数,比如上面的p1=10时,我们希望它的阈值是一个特殊值,比如可以达到200
点击热点规则配置的高级配置:
注意点:参数必须是基本类型或者String
- 多次访问值为1:
- 多次访问值为vip:
6.4 注意点:
给testHotKey方法添加int i = 1 / 0;异常。
然后重新测试,发现兜底方法不适用于异常,有异常会直接打印到页面。
因为目前的兜底方法,是运用于配置不符合的情况,对于程序出错不关心。
7. 系统规则
尽量不用,粒度太高,很容易造成整个系统瘫痪
7.1 配置系统规则:
- 针对整个系统的,每秒访问量超过1(阈值),限流
- 测试:略
8. SentnielResource:
8.1 限流演示:
8.1.1 按资源名称限流:
- 添加controller
@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 服务不可用");
}
}
- 配置限流规则:
- 测试:可以看到成功进入了我们的兜底方法
- 关闭我们的服务,刷新流控规则,可以看到,我们的流控规则消失了,说明没有进行持久化
8.1.2 按照url进行限流
- controller添加
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl") //没有兜底方法,系统就用默认的
public CommonResult byUrl() {
return new CommonResult(200,"按照byUrl限流测试",new Payment(2020L,"serial002"));
}
- 配置添加:
- 测试
8.2 存在的问题
上述演示的时候,我们可能会面临的问题:
- 如果没有写兜底方法,则是系统默认的,没有体现的我们自己的业务要求
- 如果写了兜底方法,我们自定义的处理方法又和业务代码耦合在一块,不直观
- 每个业务方法都添加一个兜底的,那代码膨胀加剧
- 全局统一的处理方法没有体现
8.3 自定义限流处理逻辑并解耦:
- 新建myhandler.CustomerBlockHandler自定义限流处理类:
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException exception) {
return new CommonResult(444,"按照客户自定义限流测试,Glogal handlerException ---- 1");
}
public static CommonResult handlerException2(BlockException exception) {
return new CommonResult(444,"按照客户自定义限流测试,Glogal handlerException ---- 2");
}
}
- 在RateLimitController中添加:
//CustomerBlockHandler
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
public CommonResult customerBlockHandler() {
return new CommonResult(200,"按照客户自定义限流测试",new Payment(2020L,"serial003"));
}
- 测试,不多说
8.4 更多注解说明:
9. Sentinel熔断处理:
9.1 新建提供者9003和9004
- 添加9003和9004
- pom导入nacos和sentinel不多说
- yml
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 10.211.55.26:8848 #nacos
management:
endpoints:
web:
exposure:
include: '*'
- 启动类不多说
- 接口:
@RestController
public class PaymentController {
@Value("${server.port}") //spring的注解
private String serverPort;
public static HashMap<Long, Payment> map = new HashMap<>();
static {
map.put(1L,new Payment(1L,"1111"));
map.put(2L,new Payment(2L,"2222"));
map.put(3L,new Payment(3L,"3333"));
}
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
Payment payment = map.get(id);
CommonResult<Payment> result = new CommonResult<>(200,"from mysql,serverPort: " + serverPort,payment);
return result;
}
}
- 测试,不多说
9.2 新建消费者84
这里远程调用服务使用ribbon来完成。
- pom、启动类和上述一样不多说
- yml
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 10.211.55.26:8848 #nacos
sentinel:
transport:
dashboard: 10.211.55.26:8858 #sentinel
port: 8719
#消费者将去访问的微服务名称
server-url:
nacos-user-service: http://nacos-payment-provider
#激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
- 启动类:
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderMain84.class,args);
}
}
- config:
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- controller:
@RestController
@Slf4j
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<Payment> result = restTemplate.getForObject(
SERVICE_URL + "/paymentSQL/" + id,CommonResult.class,id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgument,非法参数异常...");
}else if(result.getData() == null) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
}
- 简单测试,略
9.3 测试
之所以测试,是因为@SentinelResource
注解中:
- fallback管运行异常
- blockHandler管的是配置违规
9.3.1 无配置测试:
9.3.2 只配置fallback:
- 修改@SentinelResource注解,添加fallback方法:
//@SentinelResource(value = "fallback")
@SentinelResource(value = "fallback",fallback ="handlerFallback")
//。。。
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult(444,"异常handlerFallback,exception内容: " + e.getMessage(), payment);
}
- 测试:
- 结论:运行异常被兜底
9.3.3 只配置blockHandler:
- 修改注解,添加blockHandler方法
@SentinelResource(value = "fallback",blockHandler = "blockHandler")
// ...
public CommonResult blockHandler(@PathVariable Long id,BlockException e) {
Payment payment = new Payment(id,"null");
return new CommonResult(444,"blockHandler-sentinel 限流,BlockException: " + e.getMessage(), payment);
}
- 在管理端中配置降级配置:
- 测试id=5
- 多次测试:可以看到并没有进入空指针异常,而是服务被降级
- 结论:会抛出程序异常,如果触发配置异常会被降级
9.3.4 同时配置:
- 修改注解:
@SentinelResource(value = "fallback",fallback ="handlerFallback",blockHandler = "blockHandler")
- 多次调用id=1
- 测试id=5
- 多次测试id=5
- 结论:当@SentinelResource注解fallback和blockHandler都指定后,然后同时符合,优先执行blockHandler兜底方法
9.4 异常忽略:
如果出现指定异常,不会进入兜底方法,会直接报出异常。
- 修改注解:
@SentinelResource(value = "fallback",
fallback ="handlerFallback",
blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})
//如果出现exceptionsToIgnore中的异常,不运行fallback兜底方法。
- 测试
9.5 openfeign完成远程调用
上述示例远程调用是通过ribbon来完成的,这里我们使用openfeign来完成
- 修改84,添加pom、yml、启动类注解支持
- 添加service接口
// fallback为降级后调用的服务
@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);
}
- 测试
10. 规则持久化:
之前我们看到,一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化。
10.1 解决:
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。
- pom添加:
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- yml的sentinel中添加datasource配置:
sentinel:
transport:
#配置Sentin 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添加配置:
- 测试,略
- 停止后再次启动,发现配置还在
11. 熔断框架比较: