一、引言
开发过程中有些接口的跑动比较耗时耗资源,比如计算销售手底下所有门店的商品,点击过于频繁就会导致大量资源消耗,因此博主在项目中封装了一套防重防频繁的工具。
由于是分布式,所以需要依赖redis,单体服务其实可以使用本地缓存。
二、工具
1、注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisAvoid {
String key();
long avoidTime() default 10;
TimeUnit timeUnit() default TimeUnit.SECONDS;
String objectName() default "";
String[] paramName();
}
2、切面
@Aspect
@Component
public class RedisAvoidAspect {
@Resource
private RedisAvoidService redisAvoidService;
@Pointcut("@annotation(com.enmonster.bit.config.redisavoid.annotation.RedisAvoid)")
public void lockPointCut() {
}
@Around("lockPointCut() && @annotation(redisAvoid)")
public Object around(ProceedingJoinPoint joinPoint, RedisAvoid redisAvoid) throws Throwable {
String objectName = redisAvoid.objectName();
if (StringUtil.isBlank(objectName)) {
throw new BusinessException("objectName为空");
}
Object[] args = joinPoint.getArgs();
String[] objectNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Map<String, Object> objectHashMap = new HashMap<>();
for (int i = 0; i < objectNames.length; i++) {
objectHashMap.put(objectNames[i], args[i]);
}
Object o = objectHashMap.get(objectName);
String[] par = redisAvoid.paramName();
String cacheKey = redisAvoid.key();
for (int i = 0; i < par.length; i++) {
cacheKey += RedisLockCommonUtil.getFieldValueByName(par[i], o);
}
redisAvoidService.checkAndSet(redisAvoid,cacheKey);
return joinPoint.proceed();
}
}
public interface RedisAvoidService {
void checkAndSet(RedisAvoid redisAvoid, String lockKey);
}
@Service
public class RedisAvoidServiceImpl implements RedisAvoidService {
@Resource
private RedissonClient redissonClient;
private final static String LOCK_AVOID = "LOCK:AVOID:";
private final static String CACHE_AVOID = "CACHE:AVOID:";
@Override
@RedisLock(key = LOCK_AVOID
, suffixKeyTypeEnum = RedisLockCommonUtil.PARAM
, objectName = "cacheKey")
public void checkAndSet(RedisAvoid redisAvoid, String cacheKey) {
RBucket<String> bucket = redissonClient.getBucket(CACHE_AVOID + cacheKey);
if (bucket.isExists()) {
throw new BusinessException("操作过于频繁!");
}
bucket.set(cacheKey);
bucket.expire(redisAvoid.avoidTime(), redisAvoid.timeUnit());
}
}
这里使用了博主封装的分布式代理锁开源工具进行并发处理,为了项目其他人可以更加易懂,分布式代理锁的命名改为了RedisLockdistributed-proxy-lock: 分布式代理锁,动态的锁后缀采用ThreadLocal或者参数名获取锁粒度自定义选择,目前实现基于redis,后续扩展zk等
三、使用
@Override
@RedisAvoid(key = "BD_SHOP_AVOID",
objectName = "request",
paramName = {"bdCode"})
public boolean subscribe(BdRequest request) {
//业务处理
}
四、总结
这里使用防频繁工具,同时还可以作并发处理。