资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
sentinel 资源保护的实现
api 实现
引入依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.0</version> </dependency>
yml 配置
server: port: 8033 spring: cloud: nacos: discovery: server-addr: localhost:8848 application: name: sentinel-server
添加SentinelDemoController 类
package com.jiuge.study.controller; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import lombok.extern.slf4j.Slf4j; import org.checkerframework.common.reflection.qual.GetConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; @RestController @Slf4j public class SentinelDemoContoller { private static final String RESOURCE_NAME = "hello"; @GetMapping("/hello") public String hello() { Entry entry = null; try { // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。 entry = SphU.entry(RESOURCE_NAME); // 被保护的业务逻辑 String str = "hello world"; log.info(" ==== " + str); return str; } catch (BlockException e) { // 资源访问阻止,被限流或被降级 // 进行相应的处理操作 log.info("block!"); } catch (Exception ex) { // 若需要配置降级规则,需要通过这种方式记录业务异常 Tracer.traceEntry(ex, entry); } finally { if (entry != null) { entry.exit(); } } return null; } /** * 定义流控规则 */ @PostConstruct @GetConstructor private static void initFlowRules(){ List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); //设置受保护的资源 rule.setResource(RESOURCE_NAME); // 设置流控规则 QPS rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 设置受保护的资源阈值 // Set limit QPS to 20. rule.setCount(1); rules.add(rule); // 加载配置好的规则 FlowRuleManager.loadRules(rules); } }
测试验证
jmeter 配置如下 100 个请求 10s 钟请求,就是1s钟10个请求, 因为这个qps 是限制了1s1个请求,看日志结果输出是达到限流。
@SentinelResource注解实现
注解用来标识资源是否被限流、降级。
blockHandler: 定义当资源内部发生了BlockException应该进入的方法(捕获的是Sentinel定义的异常)
fallback: 定义的是资源内部发生了Throwable应该进入的方法
exceptionsToIgnore:配置fallback可以忽略的异常
引入依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>1.8.0</version> </dependency>
添加配置类 SentinelAspectConfiguration
package com.jiuge.user.config; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SentinelAspectConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } }
在UserController 请求上添加注解@SentinelResource
@RequestMapping(value = "/findOrderByUserId/{id}") @SentinelResource( value = "findOrderByUserId", fallback = "fallback", fallbackClass = MyExceptionUtil.class, blockHandler = "handlerException", blockHandlerClass = MyExceptionUtil.class ) public Mono<R> findOrderByUserId(@PathVariable("id") Integer id) { log.info("根据userId:" + id + "查询订单信息"); String url = "http://localhost:8020/order/findOrderByUserId/" + id; Mono<R> result = webClient.get().uri(url).retrieve().bodyToMono(R.class); if(id==4){ throw new IllegalArgumentException("非法参数异常"); } return result; }
编写异常请求工具类MyExceptionUtil
package com.jiuge.user.util; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.jiuge.common.utils.R; public class MyExceptionUtil { public static R fallback(Integer id, Throwable e) { return R.error(-2, "===被异常降级啦==="); } public static R handleException(Integer id, BlockException e) { return R.error(-2, "===被限流啦==="); } }
正常参数请求
{"msg":"success","code":0,"orders":[{"id":2,"userId":"2","commodityCode":"TH3332324323444","count":2,"amount":2}]}
请求ID为4的时候, 抛出异常
流控规则可以通过 Sentinel Dashboard 配置
客户端需要引入Transport 模块与sentinel 控制台继续通信
启动Sentinel Dashboard
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
不懂的可以参考其官方文档说明
https://sentinelguard.io/zh-cn/docs/dashboard.html
sentinel 整合应用
SpringCloud 整合 Sentinel
引入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
修改application.yml 内容
spring: application: name: user-server cloud: nacos: discovery: server-addr: localhost:8848 namespace: 2a57e550-6295-4269-b1b4-268c46021020 # 不适用ribbon loadbalancer: ribbon: enabled: false sentinel: transport: # 添加sentinel的控制台地址 dashboard: 127.0.0.1:8080 # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer port: 8719
在sentinel 控制台配置流控规则
请求 http://localhost:8010/user/findOrderByUserId/2验证,可以看出被流控的结果
Blocked by Sentinel (flow limiting)
RestTemplate 整合Sentinel
Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,在构造 RestTemplate bean的时候需要加上 @SentinelRestTemplate 注解。
@SentinelRestTemplate 注解的属性支持限流(blockHandler, blockHandlerClass)和降级(fallback, fallbackClass)的处理。
Sentinel RestTemplate 限流的资源规则提供两种粒度:
- httpmethod:schema://host:port/path:协议、主机、端口和路径
- httpmethod:schema://host:port:协议、主机和端口
引入依赖
<!--加入sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
自定义全局控制类
package com.jiuge.user.util; import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.jiuge.common.utils.R; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; public class GlobalExceptionUtil { /** * 注意:static修饰,参数类型不能出错 * @param request org.springframework.http.HttpRequest * @param body * @param execution * @param ex * @return */ public static SentinelClientHttpResponse handlerException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { R r = R.error(-1, "===被限流啦==="); try { return new SentinelClientHttpResponse(new ObjectMapper().writeValueAsString(r)); } catch (JsonProcessingException e) { e.printStackTrace(); } return null; } public static SentinelClientHttpResponse fallback(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { R r = R.error(-2, "===被异常降级啦==="); try { return new SentinelClientHttpResponse(new ObjectMapper().writeValueAsString(r)); } catch (JsonProcessingException e) { e.printStackTrace(); } return null; } }
在RestTemplate bean 实例方法上添加注解 @SentinelRestTemplate
/** * @author jiuge */ @Configuration public class RestConfig { @Bean @LoadBalanced @SentinelRestTemplate( blockHandler = "handleException",blockHandlerClass = GlobalExceptionUtil.class, fallback = "fallback",fallbackClass = GlobalExceptionUtil.class) public RestTemplate restTemplate() { return new RestTemplate(); } }
在UserController 上添加方法 findOrderByUserIdForRestTemplate
@GetMapping("/findOrderByUserIdForRestTemplate/{id}") public R findOrderByUserIdForRestTemplate(@PathVariable("id")Integer id){ String url = "http://order-server/order/findOrderByUserId/" + id; R result = restTemplate.getForObject(url, R.class); return result; }
在application.yml 添加配置
#true开启sentinel对resttemplate的支持,false则关闭 默认true resttemplate: sentinel: enabled: true
验证 请求 http://localhost:8010/user/findOrderByUserIdForRestTemplate/1,测试
达到阈值2的时候,会触发流控
Blocked by Sentinel (flow limiting)
OpenFeign 整合 Sentinel
引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
配置文件 application.yml 打开对sentinel 的支持
feign: sentinel: enabled: true #开启sentinel对feign的支持 默认false
修改接口 OrderFeignService,添加 fallback
package com.jiuge.user.feign; import com.jiuge.common.utils.R; import com.jiuge.user.feign.impl.FallbackOrderFeignServiceFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; /** * @author jiuge * @version 1.0 * @date 2021/8/5 10:56 */ // feignConfig 的局部配置 @FeignClient(value = "order-server", path = "order", fallbackFactory = FallbackOrderFeignServiceFactory.class) public interface OrderFeignService { @RequestMapping("/findOrderByUserId/{id}") // @RequestLine("GET /findOrderByUserId/{id}") R findOrderByUserId(@PathVariable("id") Integer id); }
添加两个实现类 ,FallbackOrderFeignService实现 OrderFeignService
package com.jiuge.user.feign.impl; import com.jiuge.common.utils.R; import com.jiuge.user.feign.OrderFeignService; import org.springframework.stereotype.Component; @Component //必须交给spring 管理 public class FallbackOrderFeignService implements OrderFeignService { @Override public R findOrderByUserId(Integer id) { return R.error(-1,"=======服务降级了========"); } }
FallbackOrderFeignServiceFactory 实现 FallbackFactory<OrderFeignService>
package com.jiuge.user.feign.impl; import com.jiuge.common.utils.R; import com.jiuge.user.feign.OrderFeignService; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component public class FallbackOrderFeignServiceFactory implements FallbackFactory<OrderFeignService> { @Override public OrderFeignService create(Throwable throwable) { return new OrderFeignService() { @Override public R findOrderByUserId(Integer id) { return R.error(-1, "===========服务降级了=========="); } }; } }
请求地址 http://localhost:8010/user/findOrderByUserIdForOpenFeign/1测试,验证输出
{"msg":"接口限流了","code":100}
dubbo 整合sentinel
引入依赖
<!--加入sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--Sentinel 对 Dubbo的适配 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-apache-dubbo-adapter</artifactId> </dependency>
在consumer 端和 provider 端 的application.yml 文件上 接入sentinel 配置
spring: cloud: sentinel: transport: # 添加sentinel的控制台地址 dashboard: 127.0.0.1:8080 #暴露actuator端点 management: endpoints: web: exposure: include: '*'
限流粒度可以是服务接口和服务方法两种粒度:
- 服务接口:resourceName 为 接口全限定名,如com.jiuge.product.service.ProductService
- 服务方法:resourceName 为 接口全限定名:方法签名,如 com.jiuge.product.service.impl.ProductServiceImpl.findById(Integer id)
Sentinel Dubbo Adapter 支持配置全局的fallback 函数
Sentinel Dubbo Adapter 还支持配置全局的 fallback 函数,可以在 Dubbo 服务被限流/降级/负载保护的时候进行相应的 fallback 处理。用户只需要实现自定义的 DubboFallback 接口,并通过 DubboAdapterGlobalConfig注册即可。默认情况会直接将 BlockException 包装后抛出。同时,我们还可以配合 Dubbo 的 fallback 机制 来为降级的服务提供替代的实现。
provider 端 在 ProductServiceImpl 类上 修改添加 下面代码 两个地方
findById 方法上添加 @SentinelResource 注解
添加方法init 注解为PostConstruct
@Override @GetMapping("/findById/{id}") @SentinelResource("findById") public ProductEntity findById(@PathVariable("id") Integer id) { return productDao.findById(id); } @PostConstruct public void init() { DubboAdapterGlobalConfig.setProviderFallback( (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult(new ProductEntity(0,"===provider fallback=="), invocation)); }
consumer 端 添加类 ProductServiceDubboMock
package com.jiuge.product.mock; import com.product.entity.ProductEntity; import com.product.service.ProductService; import java.util.List; public class ProductServiceDubboMock implements ProductService { @Override public List<ProductEntity> list() { return null; } @Override public ProductEntity findById(Integer id) { return new ProductEntity(0, "====mock==="); } }
修改ProductController 类 两个地方
在引用类ProductService类上加上注解
@DubboReference(mock = "com.jiuge.product.mock.ProductServiceDubboMock")
加入方法 init(),使用@PostConstruct
@DubboReference(mock = "com.jiuge.product.mock.ProductServiceDubboMock") private ProductService productService; @GetMapping("/info/{id}") public ProductEntity info(@PathVariable("id") Integer id) { return productService.findById(id); } @PostConstruct public void init() { DubboAdapterGlobalConfig.setConsumerFallback( (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult( new ProductEntity(0,"===fallback=="), invocation)); }
配置consumer 端的流控规则
测试结果
{"id":0,"productName":"===fallback==","productType":null,"productUnit":null}
配置provider 端的流控规则
测试结果
{"id":0,"productName":"===provider fallback==","productType":null,"product
至此,完成了sentinel dubbo 简单案例整合