Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
qiaolin-li opened this issue Jul 12, 2020 · 4 comments
Labels
area/annotation Issues or PRs related to annotation support good first issue Good for newcomers
Milestone

Comments

@qiaolin-li
Copy link

qiaolin-li commented Jul 12, 2020

Issue Description

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

Describe what you expected to happen

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<OrderCreateResult> create(TradeOrderUserRequest request) {
        return null;
    }

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

}
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<OrderCreateResult> create(TradeOrderUserRequest request) {
        log.warn("创建订单触发限流 userId:{}", request.getUserId());
        return TradeUserResultHelper.fillWithFailure(CreateCodeEnum.SENTINEL_BLOCK_IN_ERROR);
    }

    @Override
    public TradeResult<String> 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

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();

}
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<Class<?>, Object> LIMIT_OBJECT_MAP = new HashMap<>();
    private static final Map<Method, Method> 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对象和方法对象解析资源名
     * <p>格式: 类名:方法名(参数...)
     * <p>例子: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();
    }
}
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

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();
    }
}
@sczyh30 sczyh30 added the area/annotation Issues or PRs related to annotation support label Jul 15, 2020
@sczyh30
Copy link
Member

sczyh30 commented Jul 15, 2020

Good idea. We may improve existing annotation extension to support this. Discussions and contributions are welcomed!

@sczyh30 sczyh30 added the good first issue Good for newcomers label Jul 15, 2020
@qiaolin-li
Copy link
Author

So how do I do that?

@wavesZh
Copy link
Contributor

wavesZh commented Jul 15, 2020

@qiaolin-li what is the difference or enhancement for it. And it also support class level.

@qiaolin-li
Copy link
Author

@ qiaolin-li有 什么区别或增强之处。并且它还支持班级水平

Yes, it supports class-level stream-limiting

Advantages as follows

1、Automatically generate resource names to reduce the risk of duplicate names
2、Reduce method parameter mismatches
3、Reduce code coupling and promote a single responsibility

@sczyh30 sczyh30 added this to the 1.8.1 milestone Jul 21, 2020
@sczyh30 sczyh30 modified the milestones: 1.8.1, 1.8.2 Jan 18, 2021
@sczyh30 sczyh30 modified the milestones: 1.8.2, 1.8.3 Jul 5, 2021
@sczyh30 sczyh30 modified the milestones: 1.8.3, 2.0.0 Dec 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/annotation Issues or PRs related to annotation support good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

3 participants