Spring的AOP机制大家也都是耳熟能详了,代码的写法也更是简单,无非就是定义@interface 注解。通过@Aspect定义切面实现类,@Pointcut定位上面的@interface 注解,再通过@Before、@After、@Around注解分别实现 之前、之后、整体环绕的业务代码。不过大多数场景@Around更实用,可以掌控切点前后的流程,更好的实现所需业务。
下面写一个使用注解实现方法的并发控制(若要实现滑块限流,水滴限流等,其实代码流程差不多,算法不一样而已)
自定义注解 @AvoidRepeatInvoke
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AvoidRepeatInvoke {
String prefix();
}
具体实现
@Component
@Aspect
@Order(20)
public class AvoidRepeatInvokeAdvice {
/**
* Redis中存放微服务调用成功标记(footPrint)的hashCode的集合名称,一定时间后自动失效;
* 微服务方法进入时判断该标记是否存在,如存在则直接跳出,不再执行;
* 如果不存在则执行,执行成功后放入该标记.
*/
private static final String MSI_FP_PREFIX = "msi_fp_";
/**
* Redis中存放微服务调用成功的结果,有效期随footPrint.
*/
private static final String MSI_FP_VAL_PREFIX = "msi_fpval_";
/**
* Redis中存放微服务处理锁的hashCode的集合名称,一定时间后自动失效;
* 微服务方法进入时判断调用成功标记不存在之后,表示可以执行,则立即在redis中存入一个lock;
* 无论最终执行成功与否,都需要释放该lock.
*/
private static final String MSI_LOCK_PREFIX = "msi_lock_";
@DubboReference(version = "0.1")
private KVCacher kvCacher;
/**
* 服务调用足迹的过期时间,单位秒
*/
@Value("${common.service.invoke.footprint.timeout}")
private String footprintTimeout;
/**
* 服务调用足迹的过期时间,单位秒
*/
@Value("${common.service.invoke.lock.timeout}")
private String lockTimeout;
/**
* 定义PointCut
*/
@Pointcut("@annotation(com.xxx.annotation.AvoidRepeatInvoke)")
private void aspectjMethod() {
}
@Before(value = "aspectjMethod()")
public void beforeAdvice(JoinPoint joinPoint) {
//DO Nothing
}
@After(value = "aspectjMethod()")
public void afterAdvice(JoinPoint joinPoint) {
//DO Nothing
}
/**
* 环绕实际业务方法前后的处理(与before + after类似)
*
* @param pjp
* @return
* @throws Throwable
*/
@Around(value = "aspectjMethod()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
Class<?> targetClass = pjp.getTarget().getClass();
Class<?>[] paraTypes = ((MethodSignature) pjp.getSignature()).getParameterTypes();
Method method = targetClass.getMethod(methodName, paraTypes);
Type genericReturnType = method.getGenericReturnType();
Object[] args = pjp.getArgs();
LOGGER.info("拦截幂等性方法:{},参数列表:{}", methodName, pjp.getArgs());
AvoidRepeatInvoke annotation = method.getAnnotation(AvoidRepeatInvoke.class);
String prefix = annotation.prefix();
String footprint = this.getMsiFootprintKey(prefix, args, method.getName());
String fpVal = this.getMsiFootprintValue(prefix, args, method.getName());
String lock = this.getMsiLockKey(prefix, args, method.getName());
if (kvCacher.exist(lock)) {
//判断是否存在并发锁
LOGGER.info("遇到并发锁[{}],拒绝执行.", lock);
return null; //并发锁出现概率很小,直接返回null
} else {
//放入并发锁
LOGGER.debug("新建并发锁[{}]", lock);
kvCacher.set(lock, "1", Long.parseLong(lockTimeout), TimeUnit.SECONDS);
}
try {
if (kvCacher.exist(footprint)) {
//判断redis是否存在调用过的足迹,如有,直接返回上一次的结果
LOGGER.warn("存在重复锁[{}]", footprint);
String cacheResult = kvCacher.get(fpVal);
return JSON.parseObject(cacheResult, new TypeReference4Reflect(genericReturnType).getType());
}
//真正的业务逻辑调用
Object retVal = pjp.proceed(pjp.getArgs());
//成功调用后放入重复锁和运行结果,防止一定时间内的重复调用
kvCacher.set(footprint, "1", Long.parseLong(footprintTimeout), TimeUnit.SECONDS);
kvCacher.set(fpVal, JSON.toJSONString(retVal), Long.parseLong(footprintTimeout), TimeUnit.SECONDS);
LOGGER.debug("新建重复锁[{}],{}秒内不可以重复调用", footprint, footprintTimeout);
return retVal;
} finally {
//释放并发锁
LOGGER.debug("释放并发锁[{}]", lock);
kvCacher.del(lock);
}
}
大概流程就是通过redis进行累计,计算是否并发。若是限流,则直接堆redis进行累加,给个最大值,超过则返回请求失败即可。
注解使用
@ApiOperation(value = "查询")
@GetMapping(value = "/getSingle/{aa}")
@AvoidRepeatInvoke(prefix = "query_profix")
public String getSingle(@PathVariable String aa){
return "helloWorld";
}
学习文章:https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA
https://cloud.tencent.com/developer/article/1408819
令牌桶
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class RouteFilter extends ZuulFilter {
// 定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求
private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return -5;
}
@Override
public Object run() throws ZuulException {
log.info("放行");
return null;
}
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
if(!RATE_LIMITER.tryAcquire()) {
log.warn("访问量超载");
// 指定当前请求未通过过滤
context.setSendZuulResponse(false);
// 向客户端返回响应码429,请求数量过多
context.setResponseStatusCode(429);
return false;
}
return true;
}
}