限流和单元测试的例子

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,倚天版 1GB 1个月
简介: 限流和单元测试的例子

前言



最近在看限流的一些知识,并想写下单元测试。
复制代码


springboot单元测试注意事项


网络异常,图片无法展示
|


1.注意单元测试要写的位置,项目搭建后会自动有test的包,只有在test下面才能使用测试类;
2.注解中的@SpringBootTest(classes = DemoApplication.class)这里要写启动类
复制代码


限流



为什么限流


防止恶意请求浪费服务器资源
防止数据请求量过大造成服务器宕机
复制代码


限流的方法


看了一些资料后限流主要分为:
  漏桶算法 就是流入桶的速率大于流出的速度,这样才能保证桶里面有数据
         严格限制速率的,超过的速率会被打回的
  令牌桶法 一定时间内分配好令牌个数,然后再这段时间内消费这些令牌
        桶中有令牌就可以提供服务,无令牌则拒绝
  借助redis的请求数量控制
复制代码


例子



单机版方式 RateLimiter


这里可以写成注解的方式放到路由上控制访问量,再过滤器中获取令牌看是否要拒绝请求
复制代码


jar包


<dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>31.0-jre</version>
</dependency>
复制代码


测试使用


public class RateGuavaTest1 implements Runnable {
    //每秒中产生5个令牌
    private static RateLimiter limiter = RateLimiter.create(5);
    public static void exec() {
        /**这里还有有个预消费机制 
         * limiter.acquire(10);
         * 请求数量大于产生的数量会慢慢还上的
         */
        limiter.acquire(1);
        System.out.println("--" + System.currentTimeMillis() / 1000);
    }
    @Override
  public void run() {
     limiter.acquire(1);
     System.out.println(Thread.currentThread().getName()+":" + System.currentTimeMillis() / 1000);
  }
    public static void main(String[] args) {
//      for(int i=0;i<100;i++) {
//     RateGuavaTest1.exec();
//      }
      for(int i=0;i<100;i++) {
        Thread thread=new Thread(new RateGuavaTest1());
        thread.start();
      }
  }
}
复制代码


网络异常,图片无法展示
|


方法摘要


修饰符和类型 方法和描述
double acquire() 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求
double acquire(int permits) 从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求
static RateLimiter create(double permitsPerSecond) 根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) 根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
double getRate() 返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数
void setRate(double permitsPerSecond) 更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。
String toString() 返回对象的字符表现形式
boolean tryAcquire() 从RateLimiter 获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话
boolean tryAcquire(int permits) 从RateLimiter 获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话
boolean tryAcquire(int permits, long timeout, TimeUnit unit) 从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)
boolean tryAcquire(long timeout, TimeUnit unit) 从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)


集群借助redis的实现方式


踩坑


注意事项:
        这里用了redis的事务,所以再代码中返回的数据是提交执行后统一返回的。
        如果再事务执行中写了返回值
        Boolea addResut=zset.add(key, currentTime+":"+Thread.currentThread().getName(), currentTime);
        会报空指针异常的
复制代码


/**
   * @param key 限流
   * @param maxAllow 最大允许
   * @param time 间隔时间毫秒
   * @return
   */
  public boolean isRateAllow(String key,int maxAllow,int time) {
    redisTemplateString.multi();
    long currentTime=System.currentTimeMillis();
    ZSetOperations<String, Object> zset=redisTemplateString.opsForZSet();
                //添加请求
    zset.add(key, currentTime+":"+Thread.currentThread().getName(), currentTime);
            //移除到这段时间差的请求   
      zset.removeRangeByScore(key, 0,currentTime-time);
            //统计当前的请求数量
      zset.zCard(key);
      List<Object> result=redisTemplateString.exec();
      Long size=(Long)result.get(2);
      System.out.println(result+"   "+Thread.currentThread().getName()+":"+size);
    return (Long)result.get(2)<=maxAllow;
  }
复制代码


Redis-Cell


Redis4.0提供来一个限流Redis模块 —— Redis-Cell。该模块使用来漏斗算法,并提供了原子的限流指令。
复制代码


该模块只有1条指令cl.throttle,它的参数和返回值都略显复杂,接下来让我们来看看这个指令具体该如何使用。


> cl.throttle laoqian:reply 15 30 60 1
                      ▲     ▲  ▲  ▲  ▲
                      |     |  |  |  └───── need 1 quota (可选参数,默认值也是1)
                      |     |  └──┴─────── 30 operations / 60 seconds 这是漏水速率
                      |     └───────────── 15 capacity 这是漏斗容量
                      └─────────────────── key laoqian
复制代码


上面这个指令的意思是允许「用户老钱回复行为」的频率为每 60s 最多 30 次(漏水速率),漏斗的初始容量为 15,也就是说一开始可以连续回复 15 个帖子,然后才开始受漏水速率的影响。我们看到这个指令中漏水速率变成了 2 个参数,替代了之前的单个浮点数。用两个参数相除的结果来表达漏水速率相对单个浮点数要更加直观一些。


> cl.throttle laoqian:reply 15 30 60
1) (integer) 0   # 0 表示允许,1表示拒绝
2) (integer) 15  # 漏斗容量capacity
3) (integer) 14  # 漏斗剩余空间left_quota
4) (integer) -1  # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2   # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
30天前
|
测试技术 开发者
单元测试问题之测试不够如何解决
单元测试问题之测试不够如何解决
|
26天前
|
测试技术
单元测试策略问题之Mock测试的问题如何解决
单元测试策略问题之Mock测试的问题如何解决
|
26天前
|
测试技术
详解单元测试问题之@InjectMocks注解的执行逻辑如何解决
详解单元测试问题之@InjectMocks注解的执行逻辑如何解决
15 1
|
26天前
|
消息中间件 NoSQL 测试技术
单元测试问题之单元测试处理与时间相关问题如何解决
单元测试问题之单元测试处理与时间相关问题如何解决
|
26天前
|
Java 测试技术 数据库
单元测试问题之LoginServiceImpl以避免对IpUtil的依赖问题如何解决
单元测试问题之LoginServiceImpl以避免对IpUtil的依赖问题如何解决
|
26天前
|
Java 测试技术 Spring
单元测试策略问题之平衡单元测试和集成测试的问题如何解决
单元测试策略问题之平衡单元测试和集成测试的问题如何解决
|
4天前
|
测试技术
单元测试问题之在单元测试中,方法的返回值或异常,如何验证
单元测试问题之在单元测试中,方法的返回值或异常,如何验证
|
26天前
|
测试技术 Apache
单元测试策略问题之设计有效的单测用例问题如何解决
单元测试策略问题之设计有效的单测用例问题如何解决
|
30天前
|
测试技术 开发者
单元测试问题之增加代码变更如何解决
单元测试问题之增加代码变更如何解决
|
Java 测试技术 数据库
《Java单元测试实战》——无效单测:那些年,我们写过的无效单元测试(3)
《Java单元测试实战》——无效单测:那些年,我们写过的无效单元测试(3)
124 0
《Java单元测试实战》——无效单测:那些年,我们写过的无效单元测试(3)