-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Description
使用注解和抛出异常两种方式对同样的资源和规则进行流量控制时,执行结果不一样,以下是使用抛出异常方式的代码:
package com.alibaba.csp.sentinel.demo.test;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
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 java.util.ArrayList;
import java.util.List;
public class SentinelFlowThread {
public static void main(String[] args) throws InterruptedException {
initFlowRule();
for (int i = 0; i < 4; i++) {
// for (int i = 0; i < 1; i++) {
final int j = i;
new Thread(() -> {
Entry entry = null;
Object[] argArr = {"1"};
try {
if (j == 2) {
Thread.sleep(1000);
}
if (j == 3) {
Thread.sleep(4000);
}
// entry = SphU.entry("doSomething");
entry = SphU.entry("doSomething", 0, EntryType.OUT, argArr);
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 执行完成");
} catch (BlockException e) {
System.out.println(Thread.currentThread().getName() + " 被限流");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (entry != null) {
// entry.exit();
entry.exit(1, argArr);
}
}
}, "Thread-" + i).start();
}
Thread.sleep(10000);
}
private static void initFlowRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("doSomething");
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule.setCount(2);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
执行结果为:
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\Administrator\logs\csp
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
Thread-2 被限流
Thread-0 执行完成
Thread-1 执行完成
Thread-3 执行完成
以下是使用注解方式的代码:
package com.alibaba.csp.sentinel.demo.test;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
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 java.util.ArrayList;
import java.util.List;
public class TestAnnotation {
@SentinelResource(value = "doSomething", blockHandler = "usingAnnotationMethodBlockHandler")
public void usingAnnotationMethod() throws InterruptedException {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 执行完成");
}
public void usingAnnotationMethodBlockHandler(BlockException e) {
System.out.println(Thread.currentThread().getName() + " 被限流");
}
public static void main(String[] args) throws InterruptedException {
initFlowRule();
TestAnnotation testAnnotation = new TestAnnotation();
for (int i = 0; i < 4; i++) {
// for (int i = 0; i < 1; i++) {
final int j = i;
new Thread(() -> {
try {
if (j == 2) {
Thread.sleep(1000);
}
if (j == 3) {
Thread.sleep(4000);
}
testAnnotation.usingAnnotationMethod();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "Thread-" + i).start();
}
Thread.sleep(10000);
}
private static void initFlowRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("doSomething");
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule.setCount(2);
// rule.setCount(3);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
执行结果为:
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\Administrator\logs\csp
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
Thread-0 被限流
Thread-1 被限流
Thread-3 被限流
Thread-2 执行完成
然后,我把 SentinelResourceAspect 类进行修改,改完如下:
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.annotation.aspectj;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.lang.reflect.Method;
/**
* Aspect for methods with {@link SentinelResource} annotation.
*
* @author Eric Zhao
*/
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
// @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
// @Pointcut("execution(* *(..)) && @annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
@Pointcut("call(* *(..)) && @annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
// System.out.println(Thread.currentThread().getName());
Method originMethod = resolveMethod(pjp);
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();
int resourceType = annotation.resourceType();
Entry entry = null;
try {
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
return pjp.proceed();
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
throw ex;
}
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
return handleFallback(pjp, annotation, ex);
}
// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}
也就是把 pointcut 改为 @pointcut("execution(* (..)) && @annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") 或者 @pointcut("call( *(..)) && @annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)"),接着再执行使用注解方式的 TestAnnotation,执行结果为:
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\Administrator\logs\csp
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
Thread-2 被限流
Thread-1 执行完成
Thread-0 执行完成
Thread-3 执行完成
与使用抛出异常方式定义资源 SentinelFlowThread 的执行结果已经一样,这说明 AspectJ 在以方法为目标时会存在 call 和 execution 两种 join point,见官方文档 pointcuts 和 joinPoints,因此,SentinelResourceAspect 的 invokeResourceWithSentinel 方法将会被同一个线程执行两次,导致 Sentinel 统计线程数时不准确,恳请 Sentinel 的贡献者们排查这是不是一个 BUG?