1.限流锁的应用场景
同一时间接口访问量巨大,如秒杀,需要进行限流。
2.实现思路
用 CURRENT_LIMIT_+ 类名+方法名作为redis的key, value作为访问秒杀接口的人数。
用redis的计数器统计访问人数,每新增一个访问请求,计数器+1,当人数超过上限,提示服务忙,秒杀接口处理完之后,计数器-1。
3.主要组成
Dcl
:限流注解,自定义锁注解,然后给需要限流的方法加上此注解
DistributedCurrentLimit
:限流接口
RedisDistributedCurrentLimit
:限流实现类
DistributedCurrentLimitAspect
:切面类【核心】
自定义锁注解,利用切面给所有加注解的方法加分布式锁进行限流。
4.关键代码分析:
DistributedCurrentLimitAspect
:用redis计时器算人头,人头达到上限就返回服务正忙
@Around("@annotation(com.nascent.ecrp.mall.common.distribute.Dcl)")public Object distributedLock(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {if (request == null) {return proceedingJoinPoint.proceed();}MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();Method method = methodSignature.getMethod();// 用CURRENT_LIMIT_+ 类名+方法名作为redis的key,统计调用此方法的用户数String lockKey = "CURRENT_LIMIT_" + method.getDeclaringClass().getName() + "." + method.getName();Dcl distributedKey = method.getAnnotation(Dcl.class);// 解析缓存key的后缀int keyIndex = distributedKey.keyIndex();String suffixKey = "";if (keyIndex > -1) {Object[] args = proceedingJoinPoint.getArgs();suffixKey += "_" + args[keyIndex];}lockKey += suffixKey;try {// 每进来一个,redis的计数器+1Long incr = currentLimit.incr(lockKey);// 计数器数 > MAX_LIMIT,抛出异常,用户就不能继续访问接口了if (incr > MAX_LIMIT) {// currentLimit.decr(lockKey);throw new WmDefinitelyRuntimeException("前方道路拥挤,请稍后再试");}return proceedingJoinPoint.proceed();} catch (Throwable e) {throw new WmDefinitelyRuntimeException(e);} finally {currentLimit.decr(lockKey);}}
3.全部代码
Dcl
/*** 限流*/
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dcl {/*** 缓存key后缀参数所在位置** @return 位置*/int keyIndex();
}
DistributedCurrentLimit
/*** 分布式限流*/
public interface DistributedCurrentLimit{/*** 队列加1*/Long incr(String lockKey);/*** 队列减一*/Long decr(String lockKey);
RedisDistributedCurrentLimit
/*** redis 分布式锁*/
@SuppressWarnings({"UnusedReturnValue", "NullableProblems", "unused", "RedundantThrows"})
@Component
public class RedisDistributedCurrentLimit implements DistributedCurrentLimit {/*** 缓存*/@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic Long incr(String lockKey) {return redisTemplate.opsForValue().increment(lockKey);}@Overridepublic Long decr(String lockKey) {return redisTemplate.opsForValue().decrement(lockKey);}}
DistributedCurrentLimitAspect
/*** 用户操作锁Aspect*/
@Aspect
@Component
@Order(-1)
public class DistributedCurrentLimitAspect {/*** 默认最大队列,暂时写死*/private static final Long MAX_LIMIT = 50L;/*** 分布式锁*/@Autowiredprivate RedisDistributedCurrentLimit currentLimit;@Autowiredprivate HttpServletRequest request;/*** @param proceedingJoinPoint proceedingJoinPoint*/@Around("@annotation(com.nascent.ecrp.mall.common.distribute.Dcl)")public Object distributedLock(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {if (request == null) {return proceedingJoinPoint.proceed();}MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();Method method = methodSignature.getMethod();String lockKey = "CURRENT_LIMIT_" + method.getDeclaringClass().getName() + "." + method.getName();Dcl distributedKey = method.getAnnotation(Dcl.class);// 解析缓存key的后缀int keyIndex = distributedKey.keyIndex();String suffixKey = "";if (keyIndex > -1) {Object[] args = proceedingJoinPoint.getArgs();suffixKey += "_" + args[keyIndex];}lockKey += suffixKey;try {Long incr = currentLimit.incr(lockKey);if (incr > MAX_LIMIT) {// currentLimit.decr(lockKey);throw new WmDefinitelyRuntimeException("前方道路拥挤,请稍后再试");}return proceedingJoinPoint.proceed();} catch (Throwable e) {throw new WmDefinitelyRuntimeException(e);} finally {currentLimit.decr(lockKey);}}
}
秒杀接口添加锁注解
/*** 参与秒杀** @return CommonResult*/@Dul@Dcl(keyIndex = 0)@Transactional(rollbackFor = Exception.class)@Overridepublic CommonResult doSeckill(WmMarketingSeckillPostVo seckillPostVo) {String checkResult = checkGoodsSeckillInfo(seckillPostVo.getSeckillOrderInfoList(),seckillPostVo.getShopId());if(checkResult.equals(ErrorCode.SECKILL_ACTIVITY_UPDATE.getCode())){return new CommonResult().setFailed().setCode(ErrorCode.SECKILL_ACTIVITY_UPDATE.getCode()).setMsg("活动状态已变更,请重新下单");}List<JSONObject> seckillOrderList = new ArrayList<>();//下单校验List<SecKillBuyInfo> buyInfos = new ArrayList<>();SeckillOrderCheckRequest seckillOrderCheckRequest = new SeckillOrderCheckRequest();seckillOrderCheckRequest.setCustomerId(getCustomerId());seckillOrderCheckRequest.setShopId(seckillPostVo.getShopId());for(WmMarketingSeckillPostVo.SeckillOrderInfo seckillOrderInfo : seckillPostVo.getSeckillOrderInfoList()){SecKillBuyInfo secKillBuyInfo = new SecKillBuyInfo();secKillBuyInfo.setActivityId(seckillOrderInfo.getMarketingGuid());List<SeckillBuyGoodsInfo> seckillBuyGoodsInfos = new ArrayList<>();SeckillBuyGoodsInfo seckillBuyGoodsInfo = new SeckillBuyGoodsInfo();seckillBuyGoodsInfo.setGoodsId(seckillOrderInfo.getGoodsId());seckillBuyGoodsInfo.setGoodsLibId(seckillOrderInfo.getGoodsLibId());List<SeckillBuySkuInfo> seckillBuySkuInfos = new ArrayList<>();SeckillBuySkuInfo seckillBuySkuInfo = new SeckillBuySkuInfo();seckillBuySkuInfo.setGoodsSkuId(StringUtil.isBlank(seckillOrderInfo.getGoodsSkuId()) ? "0" : seckillOrderInfo.getGoodsSkuId());seckillBuySkuInfo.setBuyCount(seckillOrderInfo.getGoodsNum());seckillBuySkuInfos.add(seckillBuySkuInfo);seckillBuyGoodsInfo.setSkuInfos(seckillBuySkuInfos);seckillBuyGoodsInfos.add(seckillBuyGoodsInfo);secKillBuyInfo.setGoodsList(seckillBuyGoodsInfos);buyInfos.add(secKillBuyInfo);}seckillOrderCheckRequest.setBuyInfos(buyInfos);seckillOrderCheckRequest.setGroupId(getGroupId());SeckillOrderCheckResponse seckillOrderCheckResponse = OpenPlatformClient.exec(getGroupId(), seckillOrderCheckRequest);log.info("【调用中台下单校验接口响应结果】seckillOrderCheckResponse="+JSON.toJSONString(seckillOrderCheckResponse));if(!seckillOrderCheckResponse.success&&seckillOrderCheckResponse.getCode().equals("50402")){log.info("【秒杀下单已达限购上限】");return new CommonResult().setFailed().setCode("50402").setMsg("已达限购上限");}if(!seckillOrderCheckResponse.success&&seckillOrderCheckResponse.getCode().equals("50404")){log.info("【秒杀剩余库存不足】");return new CommonResult().setFailed().setCode("50404").setMsg("剩余库存不足");}if(!seckillOrderCheckResponse.success&&seckillOrderCheckResponse.getCode().equals("50005")){log.info("【秒杀活动已结束】");return new CommonResult().setFailed().setCode("50005").setMsg("活动已结束");}AssertUtil.assertTrue(seckillOrderCheckResponse.getSuccess()&&seckillOrderCheckResponse.getResult().isSeckillSuccess(),"秒杀活动校验失败");Map<String,ActivitySeckillCheckInfo> activityCheckInfoMap = new HashMap<>();for(ActivitySeckillCheckInfo activitySeckillCheckInfo : seckillOrderCheckResponse.getResult().getCheckInfos()){Long goodsId = activitySeckillCheckInfo.getItemInfos().get(0).getGoodsId();Long goodsLibId = activitySeckillCheckInfo.getItemInfos().get(0).getGoodsLibId();String skuId = activitySeckillCheckInfo.getItemInfos().get(0).getSkuInfos().get(0).getSkuId();//无sku也会返回到sku级,skuid为0activityCheckInfoMap.put(activitySeckillCheckInfo.getActivityId()+"_"+goodsLibId+"_"+goodsId+"_"+skuId,activitySeckillCheckInfo);}Map<String, ItemDetailsInfo> itemInfoMap = getGoodsInfos(seckillPostVo.getShopId(),seckillPostVo.getSeckillOrderInfoList());Integer expireMinute = seckillOrderCheckResponse.getResult().getExpireMinute();for(WmMarketingSeckillPostVo.SeckillOrderInfo seckillOrderInfo :seckillPostVo.getSeckillOrderInfoList()){ItemDetailsInfo itemInfo = itemInfoMap.get(seckillOrderInfo.getGoodsId()+"_"+seckillOrderInfo.getGoodsLibId());Long marketGuid = seckillOrderInfo.getMarketingGuid();Long goodsId = seckillOrderInfo.getGoodsId();Long goodsLibId = seckillOrderInfo.getGoodsLibId();String goodsSkuId = seckillOrderInfo.getGoodsSkuId()==null?"0":seckillOrderInfo.getGoodsSkuId();//无sku也会返回到sku级,skuid为0ActivitySeckillCheckInfo activitySeckillCheckInfo = activityCheckInfoMap.get(marketGuid+"_"+goodsLibId+"_"+goodsId+"_"+goodsSkuId);String activityName = activitySeckillCheckInfo.getActivityName();//秒杀价BigDecimal price = itemInfo.getPrice();for(ActivitySeckillCheckItemInfo activitySeckillCheckItemInfo : activitySeckillCheckInfo.getItemInfos()){if(activitySeckillCheckItemInfo.getGoodsId().longValue() == seckillOrderInfo.getGoodsId().longValue() && activitySeckillCheckItemInfo.getGoodsLibId().longValue() == seckillOrderInfo.getGoodsLibId().longValue()){for(ActivitySeckillCheckSkuInfo seckillCheckSkuInfo : activitySeckillCheckItemInfo.getSkuInfos()){if(seckillCheckSkuInfo.getSkuId().equals("0")||seckillCheckSkuInfo.getSkuId().equals(seckillOrderInfo.getGoodsSkuId())){price = seckillCheckSkuInfo.getPrice();}}}}JSONObject orderJson = createOrderDetail(marketGuid,activityName,expireMinute,seckillOrderInfo.getGoodsNum(), goodsId, goodsSkuId, price, itemInfo);seckillOrderList.add(orderJson);}return new CommonResult(seckillOrderList);}