alibaba / alibaba/Sentinel

Is it possible to provide a flow-limiting annotation based on the class dimension

Open
#1,598 4 comments 0 reactions 0 assignees View on GitHub
area/annotation good first issue
Dominant language
Java
Stars
23.1k
Forks
8.1k
PR merge metrics
No merged PRs in 30d

Description

## Issue Description

Is it possible to provide a flow-limiting annotation based on the class dimension

### Describe what you expected to happen
```java
package com.frxs.trade.user.service.impl;

import com.frxs.trade.user.common.util.sentinel.SentinelLimit;
import com.frxs.trade.user.common.util.sentinel.SentinelLimitParam;
import com.frxs.trade.user.core.service.config.TradeApolloConfig;
import com.frxs.trade.user.core.service.engine.TradeService;
import com.frxs.trade.user.core.service.engine.context.TradeCreateContext;
import com.frxs.trade.user.core.service.engine.context.TradeHideContext;
import com.frxs.trade.user.service.api.domain.enums.TradeActionEnum;
import com.frxs.trade.user.service.api.domain.request.trade.TradeOrderUserRequest;
import com.frxs.trade.user.service.api.domain.request.trade.TradeShieldUserRequest;
import com.frxs.trade.user.service.api.domain.result.OrderCreateResult;
import com.frxs.trade.user.service.api.domain.result.TradeResult;
import com.frxs.trade.user.service.api.facade.TradeFacade;
import com.frxs.trade.user.service.fallback.TradeFacadeFallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@SentinelLimit(TradeFacadeFallback.class)
public class TradeFacadeImpl implements TradeFacade {

@Autowired
private TradeService tradeService;

@Override
@SuppressWarnings("unchecked")
public TradeResult create(TradeOrderUserRequest request) {
return null;
}

@Override
@SuppressWarnings("unchecked")
@SentinelLimitParam(resourceName = "自定义资源名")
public TradeResult shieldOrderUser(TradeShieldUserRequest request) {
return null;
}

}
```

```java
package com.frxs.trade.user.service.fallback;

import com.frxs.trade.user.common.util.errorcode.enums.CreateCodeEnum;
import com.frxs.trade.user.common.util.errorcode.enums.UserHideOrderCodeEnum;
import com.frxs.trade.user.core.service.common.helper.TradeUserResultHelper;
import com.frxs.trade.user.service.api.domain.request.trade.TradeOrderUserRequest;
import com.frxs.trade.user.service.api.domain.request.trade.TradeShieldUserRequest;
import com.frxs.trade.user.service.api.domain.result.OrderCreateResult;
import com.frxs.trade.user.service.api.domain.result.TradeResult;
import com.frxs.trade.user.service.api.facade.TradeFacade;
import lombok.extern.slf4j.Slf4j;

/**
* xxx服务门面 限流类
* @author qiaolin
* @version $Id: TradeFacadeFallback.java,v 0.1 2019年12月27日 10:45 $Exp
*/

@Slf4j
public class TradeFacadeFallback implements TradeFacade {

@Override
public TradeResult create(TradeOrderUserRequest request) {
log.warn("创建订单触发限流 userId:{}", request.getUserId());
return TradeUserResultHelper.fillWithFailure(CreateCodeEnum.SENTINEL_BLOCK_IN_ERROR);
}

@Override
public TradeResult shieldOrderUser(TradeShieldUserRequest request) {
log.warn("删除订单触发限流 orderId:{}, userId:{}", request.getOrderId(), request.getUserId());
return TradeUserResultHelper.fillWithFailure(UserHideOrderCodeEnum.SENTINEL_BLOCK_IN_ERROR);
}

}

```

### Anything else we need to know?
I've implemented it, can I incorporate it into the framework

### The following code

```java
package com.frxs.trade.user.common.util.sentinel;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 限流注解
* @author qiaolin
* @version $Id: SentinelLimit.java,v 0.1 2019年11月19日 15:03 $Exp
*/

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SentinelLimit {

/**
* 限流时回掉的类,注意需要和被限流类具有同样签名的方法,推荐两个类实现相同的接口
*/
Class value();

}
```

```java
package com.frxs.trade.user.common.util.sentinel;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
* @author qiaolin
* @version $Id: SentinelLimitAspect.java,v 0.1 2019年11月19日 15:06 $Exp
*/

@Slf4j
@Aspect
public class SentinelLimitAspect {

private static final Map, Object> LIMIT_OBJECT_MAP = new HashMap<>();
private static final Map LIMIT_METHOD_MAP = new HashMap<>();

@Pointcut(value = "@within(com.frxs.trade.user.common.util.sentinel.SentinelLimit)")
public void pointcut(){}

@Around("pointcut()")
public Object invoke(ProceedingJoinPoint pjp) throws Throwable {
Class originClass = pjp.getTarget().getClass();
Method originMethod = resolveMethod(pjp, originClass);

SentinelLimit sentinelLimit = originClass.getAnnotation(SentinelLimit.class);
if (sentinelLimit == null) {
// Should not go through here.
throw new IllegalStateException("Wrong state for SentinelLimit annotation");
}

SentinelLimitParam sentinelLimitParam = originMethod.getAnnotation(SentinelLimitParam.class);

String resourceName = getResourceName(originClass, originMethod, sentinelLimitParam);
EntryType entryType = getEntryType(sentinelLimitParam);

Entry entry = null;
try {
entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
return handleBlockException(pjp, originMethod, sentinelLimit, ex);
} catch (Throwable ex) {
Tracer.trace(ex);
throw ex;
} finally {
if (entry != null) {
entry.exit();
}
}
}

/**
* 处理被限流的接口,调用我们的限流方法
*/
private Object handleBlockException(ProceedingJoinPoint pjp, Method originMethod, SentinelLimit sentinelLimit, BlockException ex) throws Exception {
Class clz = sentinelLimit.value();
Object o = LIMIT_OBJECT_MAP.get(clz);
if(o == null){
try {
o = clz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
log.error("实例化限流对象失败!className:{}", clz.getName(), e);
throw new RuntimeException("实例化限流对象失败", e);
}
LIMIT_OBJECT_MAP.put(clz, o);
}

return resolveFallbackMethod(pjp, clz, originMethod).invoke(o, pjp.getArgs());
}

/**
* 获取方法对象
*/
protected Method resolveMethod(ProceedingJoinPoint joinPoint, Class targetClass) {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();

Method method = getDeclaredMethodFor(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("Cannot resolve target method: " + signature.getMethod().getName());
}
return method;
}

/**
* 获取限流类的限流方法
*/
private Method resolveFallbackMethod(ProceedingJoinPoint pjp, Class fallbackClass, Method originMethod) {
if(LIMIT_METHOD_MAP.containsKey(originMethod)){
return LIMIT_METHOD_MAP.get(originMethod);
}
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = getDeclaredMethodFor(fallbackClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
String methodName = resolveMethodName(fallbackClass, originMethod);
throw new IllegalStateException("在限流类中没有找到方法:" + methodName);
}
LIMIT_METHOD_MAP.put(originMethod, method);
return method;
}

private Method getDeclaredMethodFor(Class clazz, String name, Class... parameterTypes) {
try {
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class superClass = clazz.getSuperclass();
if (superClass != null) {
return getDeclaredMethodFor(superClass, name, parameterTypes);
}
}
return null;
}

/**
* 获取资源名称
*/
private String getResourceName(Class originClass, Method method, SentinelLimitParam sentinelLimitParam){
if (sentinelLimitParam != null) {
return sentinelLimitParam.resourceName();
}
return resolveMethodName(originClass, method);
}

/**
* 获取入口类型,默认从 SentinelLimitParam 中获取,取不到直接返回 IN
*/
private EntryType getEntryType(SentinelLimitParam sentinelLimitParam){
if(sentinelLimitParam != null){
return sentinelLimitParam.entryType();
}
return EntryType.IN;
}

/**
* 根据class对象和方法对象解析资源名
*

格式: 类名:方法名(参数...)
*

例子:java.lang.String:indexOf(int)
*/
private String resolveMethodName(Class originClass, Method method) {
String className = originClass.getName();
String methodName = method.getName();
StringBuilder sb = new StringBuilder();
sb.append(className);
sb.append(":");
sb.append(methodName);
sb.append("(");
Class[] parameterTypes = method.getParameterTypes();
if(ArrayUtils.isNotEmpty(parameterTypes)){
for (Class parameterType : parameterTypes) {
sb.append(parameterType.getSimpleName() );
sb.append(", ");
}
sb.delete(sb.length()-2, sb.length());
}
sb.append(")");
return sb.toString();
}
}

```

```java
package com.frxs.trade.user.common.util.sentinel;

import com.alibaba.csp.sentinel.EntryType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 限流具体参数,用于在方法上
* @author qiaolin
* @version $Id: InLimit.java,v 0.1 2019年11月19日 15:23 $Exp
*/

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SentinelLimitParam {

/**
* 自定义资源名
*/
String resourceName();

/**
* 自定义 限流类型
*/
EntryType entryType() default EntryType.IN;

}
```

### in use

```java
package com.frxs.trade.user.core.service.config;

import com.frxs.trade.user.common.util.sentinel.SentinelLimitAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* sentinel 配置
* @author qiaolin
* @version $Id: SentinelConfig.java,v 0.1 2019年11月19日 17:18 $Exp
*/

@Configuration
public class SentinelConfig {

@Bean
public SentinelLimitAspect sentinelLimitAspect(){
return new SentinelLimitAspect();
}
}
````

Contributor guide

Open the contributing guide

Research direction

Start with SentinelLimit.java, SentinelLimitParam.java, and SentinelLimitAspect.java to understand the existing class- and method-level annotations and interception flow; SentinelConfig.java shows the Spring registration point. Determine how the requested class-dimension flow limiting should fit the framework and what integration behavior must be accepted, using the provided implementation as context.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.