cover_image

Sentinel: 使用注解限流

尹吉欢 猿天地
2019年03月12日 00:00

在前面我们对Sentinel做了一个详细的介绍,可以手动的通过Sentinel提供的SphU类来保护资源。

文章查看:Sentinel: 分布式系统的流量防卫兵

这种做法不好的地方在于每个需要限制的地方都得写代码,从 0.1.1 版本开始,Sentinel 提供了 @SentinelResource 注解的方式,非常方便。

要使用注解来保护资源需要引入下面的Maven依赖:

  1. <dependency>

  2. <groupId>com.alibaba.csp</groupId>

  3. <artifactId>sentinel-annotation-aspectj</artifactId>

  4. <version>1.4.1</version>

  5. </dependency>

引入之后我们需要配置SentinelResourceAspect切面让其生效,因为是通过SentinelResourceAspect切面来实现的,我这边以Spring Boot中使用进行配置示列:

  1. import org.springframework.context.annotation.Bean;

  2. import org.springframework.context.annotation.Configuration;

  3. import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;


  4. @Configuration

  5. public class AopConfiguration {


  6. @Bean

  7. public SentinelResourceAspect sentinelResourceAspect() {

  8. return new SentinelResourceAspect();

  9. }


  10. }

然后在需要限制的方法上加SentinelResource注解即可:

  1. @SentinelResource(value = "get", blockHandler = "exceptionHandler")

  2. @Override

  3. public String get(String id) {

  4. return "http://cxytiandi.com";

  5. }


  6. public String exceptionHandler(String id, BlockException e) {

  7. e.printStackTrace();

  8. return "错误发生在" + id;

  9. }

SentinelResource:value

表示资源名,必填项

SentinelResource:blockHandler

处理 BlockException 的方法名,可选项。若未配置,则将 BlockException 直接抛出。

  • blockHandler 函数访问范围需要是 public

  • 返回类型需要与原方法相匹配

  • 参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException

  • blockHandler 函数默认需要和原方法在同一个类中

如果你不想让异常处理方法跟业务方法在同一个类中,可以使用 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

业务方法:

  1. @SentinelResource(value = "get2", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class })

  2. @Override

  3. public String get2() {

  4. return "http://cxytiandi.com";

  5. }

异常处理类:

  1. import com.alibaba.csp.sentinel.slots.block.BlockException;


  2. public final class ExceptionUtil {


  3. public static String handleException(BlockException ex) {

  4. System.err.println("错误发生: " + ex.getClass().getCanonicalName());

  5. return "error";

  6. }


  7. }

如何测试?

我们可以在Spring Boot的启动类中定义规则,然后快速访问接口,就可以看出效果啦,或者用压力测试工具ab等。

  1. @SpringBootApplication

  2. public class App {

  3. public static void main(String[] args) {

  4. initFlowRules();

  5. SpringApplication.run(App.class, args);

  6. }


  7. private static void initFlowRules() {

  8. List<FlowRule> rules = new ArrayList<>();


  9. FlowRule rule = new FlowRule();

  10. rule.setResource("get");

  11. rule.setGrade(RuleConstant.FLOW_GRADE_QPS);

  12. rule.setCount(1);

  13. rules.add(rule);


  14. rule = new FlowRule();

  15. rule.setResource("get2");

  16. rule.setGrade(RuleConstant.FLOW_GRADE_QPS);

  17. rule.setCount(1);

  18. rules.add(rule);


  19. FlowRuleManager.loadRules(rules);

  20. }

  21. }

源码分析

只需要配置了SentinelResourceAspect就可以使用注解,我们来简单的看下SentinelResourceAspect的源码

  1. @Aspect

  2. public class SentinelResourceAspect extends AbstractSentinelAspectSupport {


  3. @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")

  4. public void sentinelResourceAnnotationPointcut() {

  5. }


  6. @Around("sentinelResourceAnnotationPointcut()")

  7. public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {

  8. // 获取当前访问的方法

  9. Method originMethod = resolveMethod(pjp);

  10. // 获取方法上的SentinelResource注解

  11. SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);

  12. if (annotation == null) {

  13. // Should not go through here.

  14. throw new IllegalStateException("Wrong state for SentinelResource annotation");

  15. }

  16. // 获取资源名

  17. String resourceName = getResourceName(annotation.value(), originMethod);

  18. EntryType entryType = annotation.entryType();

  19. Entry entry = null;

  20. try {

  21. entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());

  22. Object result = pjp.proceed();

  23. return result;

  24. } catch (BlockException ex) {

  25. // 处理被限制的异常,回调事先配置的异常处理方法

  26. return handleBlockException(pjp, annotation, ex);

  27. } catch (Throwable ex) {

  28. Tracer.trace(ex);

  29. throw ex;

  30. } finally {

  31. if (entry != null) {

  32. entry.exit();

  33. }

  34. }

  35. }

  36. }

上面是整个切面的代码,对所有加了SentinelResource注解的方法进去切入。细节代码在AbstractSentinelAspectSupport中,大家自己去看看。

欢迎加入我的知识星球,一起交流技术,免费学习猿天地的课程(http://cxytiandi.com/course)

PS:目前星球中正在星主的带领下组队学习Sentinel,等你哦!

图片

图片


继续滑动看下一个
猿天地
向上滑动看下一个