共计 3458 个字符,预计需要花费 9 分钟才能阅读完成。
在前面我们对 Sentinel 做了一个详细的介绍,可以手动的通过 Sentinel 提供的 SphU 类来保护资源。这种做法不好的地方在于每个需要限制的地方都得写代码,从 0.1.1 版本开始,Sentinel 提供了 @SentinelResource 注解的方式,非常方便。
要使用注解来保护资源需要引入下面的 Maven 依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.4.1</version>
</dependency>
引入之后我们需要配置 SentinelResourceAspect 切面让其生效,因为是通过 SentinelResourceAspect 切面来实现的,我这边以 Spring Boot 中使用进行配置示列:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
@Configuration
public class AopConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
然后在需要限制的方法上加 SentinelResource 注解即可:
@SentinelResource(value = “get”, blockHandler = “exceptionHandler”)
@Override
public String get(String id) {
return “http://cxytiandi.com”;
}
public String exceptionHandler(String id, BlockException e) {
e.printStackTrace();
return “ 错误发生在 ” + id;
}
SentinelResource:value
表示资源名,必填项
SentinelResource:blockHandler
处理 BlockException 的方法名,可选项。若未配置,则将 BlockException 直接抛出。
blockHandler 函数访问范围需要是 public
返回类型需要与原方法相匹配
参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException
blockHandler 函数默认需要和原方法在同一个类中
如果你不想让异常处理方法跟业务方法在同一个类中,可以使用 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
业务方法:
@SentinelResource(value = “get2”, blockHandler = “handleException”, blockHandlerClass = { ExceptionUtil.class})
@Override
public String get2() {
return “http://cxytiandi.com”;
}
异常处理类:
import com.alibaba.csp.sentinel.slots.block.BlockException;
public final class ExceptionUtil {
public static String handleException(BlockException ex) {
System.err.println(“ 错误发生: ” + ex.getClass().getCanonicalName());
return “error”;
}
}
如何测试?
我们可以在 Spring Boot 的启动类中定义规则,然后快速访问接口,就可以看出效果啦,或者用压力测试工具 ab 等。
@SpringBootApplication
public class App {
public static void main(String[] args) {
initFlowRules();
SpringApplication.run(App.class, args);
}
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource(“get”);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
rules.add(rule);
rule = new FlowRule();
rule.setResource(“get2”);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
源码分析
只需要配置了 SentinelResourceAspect 就可以使用注解,我们来简单的看下 SentinelResourceAspect 的源码
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
@Pointcut(“@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)”)
public void sentinelResourceAnnotationPointcut() {
}
@Around(“sentinelResourceAnnotationPointcut()”)
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
// 获取当前访问的方法
Method originMethod = resolveMethod(pjp);
// 获取方法上的 SentinelResource 注解
SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
if (annotation == null) {
// Should not go through here.
throw new IllegalStateException(“Wrong state for SentinelResource annotation”);
}
// 获取资源名
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
Entry entry = null;
try {
entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
// 处理被限制的异常,回调事先配置的异常处理方法
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
Tracer.trace(ex);
throw ex;
} finally {
if (entry != null) {
entry.exit();
}
}
}
}
上面是整个切面的代码,对所有加了 SentinelResource 注解的方法进去切入。细节代码在 AbstractSentinelAspectSupport 中,大家自己去看看。
欢迎加入我的知识星球,一起交流技术,免费学习猿天地的课程(http://cxytiandi.com/course)
PS:目前星球中正在星主的带领下组队学习 Sentinel,等你哦!