0


【SpringBoot】秒杀业务:redis+拦截器+自定义注解+验证码简单实现限流

在这里插入图片描述

** 🏡浩泽学编程:个人主页
** 🔥 推荐专栏
:《深入浅出SpringBoot》《java对AI的调用开发》
《RabbitMQ》《Spring》《SpringMVC》《项目实战》🛸学无止境,不骄不躁,知行合一

文章目录


前言

限流是秒杀业务最常用的手段。限流是从用户访问压力的角度来考虑如何应对系统故障。这里我是用限制访问接口次数(Redis+拦截器+自定义注解)和验证码的方式实现简单限流。


一、接口限流

  • 接口限流是为了对服务端的接口接收请求的频率进行限制,防止服务挂掉。
  • 栗子:假设我们的秒杀接口一秒只能处理12w个请求,结果秒杀活动刚开始就一下来了20w个请求。这肯定是不行的,我们可以通过接口限流将这8w个请求给拦截住,不然系统直接就整挂掉。
  • 实现方案: - Sentiel等开源流量控制组件(Sentiel主要以流量为切入点,提供流量控制、熔断降级、系统自适应保护等功能的稳定性和可用性)- 秒杀请求之前进行验证码输入或答题等- 限制同一用户、ip单位时间内请求次数- 提前预约- 等等

这里我使用的是Redis+Lua脚本+拦截器实现同一用户单位时间内请求次数限制。

自定义注解

含义:限制xx秒内最多请求xx次

importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/**
 * @Version: 1.0.0
 * @Author: Dragon_王
 * @ClassName: AccessLimit
 * @Description: 通用接口限流,限制xx秒内最多请求次数
 * @Date: 2024/3/3 17:09
 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public@interfaceAccessLimit{//时间,单位秒intsecond();//限制最大请求次数intmaxCount();//是否需要登录booleanneedLogin()defaulttrue;}

Redis+Lua脚本+拦截器

主要关心业务逻辑:

@ComponentpublicclassAccessLimitInterceptorimplementsHandlerInterceptor{@AutowiredprivateIUserService userService;@AutowiredprivateRedisTemplate redisTemplate;//加载lua脚本privatestaticfinalDefaultRedisScript<Boolean>SCRIPT;static{SCRIPT=newDefaultRedisScript<>();SCRIPT.setLocation(newClassPathResource("script.lua"));SCRIPT.setResultType(Boolean.class);}@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{if(handler instanceofHandlerMethod){//获取登录用户User user =getUser(request, response);HandlerMethod hm =(HandlerMethod) handler;//获取自定义注解内的属性值AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);if(accessLimit ==null){returntrue;}int second = accessLimit.second();int maxCount = accessLimit.maxCount();boolean needLogin = accessLimit.needLogin();//获取当前请求地址作为keyString key = request.getRequestURI();//如果needLogin=true,是必须登录,进行用户状态验证if(needLogin){if(user ==null){render(response,RespBeanEnum.SESSION_ERROR);returnfalse;}
                key +=":"+ user.getId();}//使用lua脚本Object result = redisTemplate.execute(SCRIPT,Collections.singletonList(key),newString[]{String.valueOf(maxCount),String.valueOf(second)});if(result.equals(false)){//render函数就是一个让我返回报错的函数,这里的RespBeanEnum是我封装好的报错的枚举类型,无需关注,render函数你也无需管,只要关心return false拦截render(response,RespBeanEnum.ACCESS_LIMIT_REACHED);//拦截returnfalse;}}returntrue;}privatevoidrender(HttpServletResponse response,RespBeanEnum respBeanEnum)throwsIOException{
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");PrintWriter printWriter = response.getWriter();RespBean bean =RespBean.error(respBeanEnum);
        printWriter.write(newObjectMapper().writeValueAsString(bean));
        printWriter.flush();
        printWriter.close();}/**
     * @Description: 获取当前登录用户
     * @param request
     * @param response
     * @methodName: getUser
     * @return: com.example.seckill.pojo.User
     * @Author: dragon_王
     * @Date: 2024-03-03 17:20:51
     */privateUsergetUser(HttpServletRequest request,HttpServletResponse response){String userTicket =CookieUtil.getCookieValue(request,"userTicker");if(StringUtils.isEmpty(userTicket)){returnnull;}return userService.getUserByCookie(userTicket, request, response);}}

lua脚本,如果第一次访问就存入计数器,每次访问+1,如果计数器大于5返回false

local key = KEYS[1]local maxCount =tonumber(ARGV[1])local second =tonumber(ARGV[2])local count = redis.call('GET', key)if count then
    count =tonumber(count)if count < maxCount then
        count = count +1
        redis.call('SET', key, count)
        redis.call('EXPIRE', key, second)elsereturnfalseendelse
    redis.call('SET', key,1)
    redis.call('EXPIRE', key, second)endreturntrue

在这里插入图片描述

二、验证码

引入验证码依赖(这是个开源的图形验证码,直接拿过来用):

<!--验证码依赖--><dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version></dependency><dependency><groupId>org.openjdk.nashorn</groupId><artifactId>nashorn-core</artifactId><version>15.3</version></dependency>
/**
     * @Description: 获取验证码
     * @param user
     * @param goodsId
     * @param response
     * @methodName: verifyCode
     * @return: void
     * @Author: dragon_王
     * @Date: 2024-03-03 12:38:14
     */@ApiOperation("获取验证码")@GetMapping(value ="/captcha")publicvoidverifyCode(User user,Long goodsId,HttpServletResponse response){if(user ==null|| goodsId <0){thrownewGlobalException(RespBeanEnum.REQUEST_ILLEGAL);}//设置请求头为输出图片的类型
        response.setContentType("image/jpg");
        response.setHeader("Pargam","No-cache");
        response.setHeader("Cache-Control","no-cache");
        response.setDateHeader("Expires",0);//生成验证码ArithmeticCaptcha captcha =newArithmeticCaptcha(130,32,3);//奖验证码结果存入redis
        redisTemplate.opsForValue().set("captcha:"+ user.getId()+":"+ goodsId, captcha.text(),300,TimeUnit.SECONDS);try{
            captcha.out(response.getOutputStream());}catch(IOException e){
            log.error("验证码生成失败", e.getMessage());}}

这里用的是bootstrap写的简单前端:

<divclass="row"><divclass="form-inline"><imgid="captchaImg"width="130"height="32"style="display: none"onclick="refreshCaptcha()"/><inputid="captcha"class="form-control"style="display: none"/><buttonclass="btn btn-primary"type="button"id="buyButton"onclick="getSeckillPath()">立即秒杀
        </button></div></div><script>

校验验证码逻辑也很简单 (从redis中取出存入的图形结果和输入框中比对):

/**
     * @Description: 校验验证码
     * @param user
     * @param goodsId
     * @param captcha
     * @methodName: checkCaptcha
     * @return: boolean
     * @Author: dragon_王
     * @Date: 2024-03-03 15:48:13
     */publicbooleancheckCaptcha(User user,Long goodsId,String captcha){if(user ==null|| goodsId <0||StringUtils.isEmpty(captcha)){returnfalse;}String redisCaptcha =(String) redisTemplate.opsForValue().get("captcha:"+ user.getId()+":"+ goodsId);return captcha.equals(redisCaptcha);}

在这里插入图片描述


总结

以上就是用redis+自定义注解+Lua脚本+拦截器限制访问接口次数和验证码的方式实现简单限流。


本文转载自: https://blog.csdn.net/weixin_62951900/article/details/136488355
版权归原作者 浩泽学编程 所有, 如有侵权,请联系我们删除。

“【SpringBoot】秒杀业务:redis+拦截器+自定义注解+验证码简单实现限流”的评论:

还没有评论