分布式锁实现限流

发布时间:2024年01月15日

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的计数器+1
            Long 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 {


    /**
     * 缓存
     */
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public Long incr(String lockKey) {
        return redisTemplate.opsForValue().increment(lockKey);
    }

    @Override
    public 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;

    /**
     * 分布式锁
     */
    @Autowired
    private RedisDistributedCurrentLimit currentLimit;

    @Autowired
    private 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)
    @Override
    public 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为0
            activityCheckInfoMap.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为0
            ActivitySeckillCheckInfo 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);
    }
文章来源:https://blog.csdn.net/qq_45807943/article/details/135607031
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。