【性能优化,打造亿级秒杀系统】- (八)防刷限流

概述

本章介绍了常见的黄牛入侵手段,以及如何使用对应的防刷手段防止黄牛入侵。同时业务的发展预估永远可能高于系统可承载的能力,因此介绍了使用多种限流技术保证系统的稳定。

本章学习目标:

  • 掌握验证码生成与验证技术;
  • 掌握限流原理与实现;
  • 掌握防黄牛技术;

一. 验证码

  • 包装秒杀令牌设置,需要验证码来错峰,分散用户的请求;
  • 数学公式验证码生成器;

1.1 代码实现

首先我们需要一个生成二维码的程序,用awt来写。然后是在ordercontroller加上二维码的验证

OrderController.java
***
//生成秒杀令牌前,需要接收验证码
//生成验证码
    @RequestMapping(value = "/generateverifycode",method = {RequestMethod.GET,RequestMethod.POST})
    @ResponseBody
    public void generateverifycode(HttpServletResponse response) throws BusinessException, IOException {
        String token = httpServletRequest.getParameterMap().get("token")[0];
        if(StringUtils.isEmpty(token)){
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登陆,不能生成验证码");
        }
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
        if(userModel == null){
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登陆,不能生成验证码");
        }

        Map<String,Object> map = CodeUtil.generateCodeAndPic();

        redisTemplate.opsForValue().set("verify_code_"+userModel.getId(),map.get("code"));
        redisTemplate.expire("verify_code_"+userModel.getId(),10,TimeUnit.MINUTES);

        ImageIO.write((RenderedImage) map.get("codePic"), "jpeg", response.getOutputStream());


    }

    //生成秒杀令牌
    @RequestMapping(value = "/generatetoken",method = {RequestMethod.POST},consumes={CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType generatetoken(@RequestParam(name="itemId")Integer itemId,
                                        @RequestParam(name="promoId")Integer promoId,
                                          @RequestParam(name="verifyCode")String verifyCode) throws BusinessException {
     //通过verifycode验证验证码的有效性
        String redisVerifyCode = (String) redisTemplate.opsForValue().get("verify_code_"+userModel.getId());
        if(StringUtils.isEmpty(redisVerifyCode)){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"请求非法");
        }
        if(!redisVerifyCode.equalsIgnoreCase(verifyCode)){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"请求非法,验证码错误");
        }
     } 

二. 限流方案

我们的原则:

  • 流量远比你想象的要多
  • 系统能运行或者总比挂了要好
  • 宁愿让少数人能用,也不要让所有人不能用

2.1 限并发

限制并发量意思就是同一时间只有一定数量的线程去处理请求,实现也比较简单,维护一个全局计数器,当请求进入接口时,计数器-1,并且判断计数器是否>0,大于0则处理请求,小于0则拒绝等待。

但是一般衡量并发性,是用TPS或者QPS,而该方案由于限制了线程数,自然不能用TPS或者QPS衡量。

2.2 令牌桶/漏桶

  • 令牌桶
    客户端请求接口,必须先从令牌桶中获取令牌,令牌是由一个“定时器”定期填充的。在一个时间内,令牌的数量是有限的。令牌桶的大小为100,那么TPS就为100。

该方式可以处理突发流量

  • 漏桶
    客户端请求接口,会向漏桶里面“加水”。漏桶每秒漏出一定数量的“水”,也就是处理请求。只有当漏洞不满时,才能请求。刚开始时该桶是满的

该方式主要用来处理平滑流量

三. 限流力度

  • 接口维度
  • 总维度

接口维度就是限制某个接口的流量,而总维度是所有接口的流量。

四. 限流范围

  • 集群限流:依赖Redis或其它中间件技术做统一计数器,往往会产生性能瓶颈
  • 单机限流:负载均衡的前提下单机平均限流效果更好

五. 代码实现

google.guava.RateLimiter就是令牌桶算法的一个实现类,OrderController引入这个类,在init方法里面,初始令牌数量为200。

@PostConstruct
    public void init() {
    //20个线程的线程池
    executorService = Executors.newFixedThreadPool(20);
    //200个令牌,即200TPS
    orderCreateRateLimiter = RateLimiter.create(200);
}

请求createOrder接口之前,会调用RateLimiter.tryAcquire方法,看当前令牌是否足够,不够直接抛出异常。

if (!orderCreateRateLimiter.tryAcquire())
     throw new BizException(EmBizError.RATELIMIT);

六. 防刷技术

排队、限流、令牌只能控制总流量,无法控制黄牛流量。
传统防刷技术

  • 限制一个会话(Session、Token)一定时间内请求接口的次数。多会话接入绕开无效,比如黄牛可以开启多个会话。
  • 限制一个IP一定时间内请求接口的次数。容易误伤,某个局域网的正常用户共享一个IP进行访问。而且IP可以被伪造。
    黄牛为什么难防
  • 模拟硬件设备,比如手机。一个看似正常的用户,可能是用模拟器模拟出来的。
  • 设备牧场,一屋子手机刷接口。
  • 人工作弊,这个最难防,请真人刷接口。

6.1 防黄牛方案

  • 设备指纹方式:采集终端设备各项数据,启动应用时生成一个唯一设备指纹。根据对应设备的指纹参数,估计是可疑设备的概率。
  • 凭证系统:根据设备指纹下发凭证,在关键业务链路上带上凭证并由凭证服务器验证。凭证服务器根据设备指纹参数和风控系统判定凭证的可疑程度。若凭证分数低于设定值,则开启验证。

七. 小结

这一节我们

  • 通过引入验证码技术,在发送秒杀令牌之前,再做一层限流。
  • 介绍了三种限流的方案,使用RateLimiter实现了令牌桶限流。
  • 介绍了常见的防刷技术以及它们的缺点。介绍了黄牛为什么难防,应该怎样防。
发布了118 篇原创文章 · 获赞 5 · 访问量 8732

猜你喜欢

转载自blog.csdn.net/weixin_43672855/article/details/104664386