【知识】基于Guava限流

Guava限流

# 限流场景

  • 12306
  • 双十一(11.11零点,由于各种商家的促销活动(前XX名免单),支付宝进入排队支付状态)
  • 外卖业务

# 常见的限流方案

# 手动实现负载均衡

# 验证码

12306故意把验证码弄的模糊不清,影响消费者下单,从而限流

# 容器限流

  • 以Tomcat容器为例,其Connector其中一种配置有如下几个参数
  • acceptCount:如果Tomcat的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接
  • maxConnections:瞬时最大连接数,超出的会排队等待
  • maxThreads:Tomcat能用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数则可能会僵死

# 限流总资源数

如果有的资源是稀缺资源(如数据库连接、线程),而且可能有多个系统都会去使用它,那么需要限制应用
可以使用池化技术来限制总资源数:连接池、线程池
比如分配给每个应用的数据库连接是100
那么本应用最多可以使用100个资源,超出了可以等待或者抛异常

# 限流某个接口的总并发/请求数

如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,如抢购业务;
这个时候就需要限制这个接口的总并发请求数了;
因为粒度比较细,可以为每个接口都设置相应的阈值。可以使用Java中的AtomicLong进行限流

try {
    if (atomic.incrementAndGet() > 限流数) {
        // 拒绝请求
    }
    // 处理请求
} finally {
    atomic.decrementAndGet();
}
1
2
3
4
5
6
7
8

# Nginx限流

Nginx提供了一个叫ngx_http_limit_req_module的模块进行流量控制

# 消息队列

通过RabbitMQ,RocketMQ,ActiveMQ,ZeroMQ,Kafka把流量做均匀,限制高流量涌入

# 利用Netflix的Hystrix限流

# Guava是什么

Guava是一个Google开发的基于java的类库集合的扩展项目,包括collectiions,caching,primitives support,concurrency libraries,common annotations,string processing,I/O等等,这些高质量的API可以使你的JAVA代码更加优雅,更加简洁,让你工作更加轻松愉快
限流的概念

# Guava限流核心算法

漏桶算法 令牌桶算法 比较

# Guava限流实战

Guava RateLimiter实现平滑限流

  • 平滑突发限流(SmoothBursty)
import com.google.common.util.concurrent.RateLimiter;

/**
 * @Description 平滑突发限流(SmoothBursty)
 */
public class SmoothBurstyRateLimitTest01 {

    public static void main(String[] args) {
        //每秒允许5个请求,表示桶容量为5且每秒新增5个令牌,即每隔0.2秒新增一个令牌
        RateLimiter limiter = RateLimiter.create(5);
        //如果当前桶中有足够令牌则成功(返回值为0)返回获取token的耗时,以秒为单位
        //将突发请求速率平均为了固定请求速率,固定频率=0.2s/个
        System.out.println(limiter.acquire(1));
        System.out.println(limiter.acquire(1));
        System.out.println(limiter.acquire(1));
        System.out.println(limiter.acquire(1));
        System.out.println(limiter.acquire(1));
        System.out.println(limiter.acquire(1));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import com.google.common.util.concurrent.RateLimiter;

/**
 * @Description 平滑突发限流(SmoothBursty) 使用场景:对冷数据的预热处理
 */
public class SmoothBurstyRateLimitTest02 {

    public static void main(String[] args) {
        //每秒允许5个请求,表示桶容量为5且每秒新增5个令牌,即每隔0.2秒新增一个令牌
        RateLimiter limiter = RateLimiter.create(5);
        //一次性消费5个令牌,模拟预热的场景(初始化redis缓存)
        System.out.println(limiter.acquire(5));
        //limiter.acquire(1)将等待差不多1秒桶中才能有令牌
        System.out.println(limiter.acquire(1));
        //固定速率
        System.out.println(limiter.acquire(1));
        //固定速率
        System.out.println(limiter.acquire(1));
        //固定速率
        System.out.println(limiter.acquire(1));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

import com.google.common.util.concurrent.RateLimiter;

/**
 * @Description 平滑突发限流(SmoothBursty) 使用场景:对冷数据的预热处理
 */
public class SmoothBurstyRateLimitTest03 {

    public static void main(String[] args) {
        //每秒允许5个请求,表示桶容量为5且每秒新增5个令牌,即每隔0.2毫秒新增一个令牌
        RateLimiter limiter = RateLimiter.create(5);
        //第一秒突发了10个请求
        System.out.println(limiter.acquire(10));
        //limiter.acquire(1)将等待差不多2秒桶中才能有令牌
        System.out.println(limiter.acquire(1));
        //固定速率
        System.out.println(limiter.acquire(1));
        //固定速率
        System.out.println(limiter.acquire(1));
        //固定速率
        System.out.println(limiter.acquire(1));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

  • 平滑预热限流(SmoothWarmingUp)
import com.google.common.util.concurrent.RateLimiter;

import java.util.concurrent.TimeUnit;

/**
 * @Description 平滑预热限流(SmoothWarmingUp)
 */
public class SmoothWarmingUp {

    public static void main(String[] args) {

        //permitsPerSecond:每秒新增的令牌数  warmupPeriod:从冷启动速率过渡到平均速率的时间间隔
        //系统冷启动后慢慢的趋于平均固定速率(即刚开始速率慢一些,然后慢慢趋于我们设置的固定速率)
        RateLimiter limiter = RateLimiter.create(10, 1000, TimeUnit.MILLISECONDS);
        for(int i = 0; i < 20;i++) {
            //获取一个令牌
            System.out.println(limiter.acquire(1));
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

  • Guava用于秒杀场景
import com.google.common.util.concurrent.RateLimiter;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @Description Guava秒杀场景:Guava使得商品被均匀的被秒杀,效果较好,未秒到商品的用户引流至推荐页
 */
public class GuavaSecKill {

    public static void main(String[] args) throws InterruptedException {

        //限流,每秒允许10个请求进入秒杀 QPS=10 令牌生成速度=100ms/个
        RateLimiter limiter = RateLimiter.create(0);
        /**
         * 开线程池
         */
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        //100个线程同时抢购
        for (int i = 0; i < 100; i++) {

            executorService.submit(() -> {
                //每个秒杀请求如果在50ms(0.05s)以内得到令牌,就算是秒杀成功,否则就返回秒杀失败
                if (limiter.tryAcquire(50, TimeUnit.MILLISECONDS)) {
                    if (CountUtils.TOTAL_COUNT.get() > 0) {
                        //库存减1
                        CountUtils.decrease();
                        System.out.println("恭喜您,秒杀成功!!!");
                    } else {
                        System.out.println("秒杀失败,商品已售完");
                    }

                } else {
                    System.out.println("秒杀失败,请继续努力~");
                }
            });
            //给limiter 0.01s的时间生成新的令牌生成
            Thread.sleep(10);
        }
        Thread.sleep(1000);
        executorService.shutdown();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

Guava对比其他限流方案的特点

# 备注

参考: https://github.com/online-demo/yunxi-guava/tree/master/src/main/java/com/yunxi/demo/guava (opens new window)