02.RateLimiter

1.作用

跟JDK的信号量Semaphore作用比较像,Semaphore用于控制,如下代码允许被多个线程同时访问的线程数量:

try {
    semaphore.acquire();
    //被控制代码....
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    semaphore.release();
}

而RateLimiter用于控制1秒钟允许操作代码块的次数(也就是控制操作速率(匀速))的

package com.mzj.guava.concurrent.ratelimiter;

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

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class RateLimiterExample {

    /**
     * 1秒钟只允许有0.5次操作,也就是只允许2秒执行一次操作(testLimiter方法)
     */
    private final static RateLimiter limiter = RateLimiter.create(0.5);

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        IntStream.range(0,10).forEach(i -> service.submit(RateLimiterExample::testLimiter));
    }

    private static void testLimiter(){
        System.out.println(Thread.currentThread().getName() + " waiting " + limiter.acquire());
        //被控制的代码。。。
    }
}

代码示例

https://github.com/mazhongjia/googleguava/tree/master/src/main/java/com/mzj/guava/concurrent/ratelimiter

2、典型应用场景

2.1 使用RateLimiter实现漏桶算法

背景:虽然可以通过水平扩展的方式(增加业务系统服务器节点数量)扩容业务系统,使得业务系统可以承载更多的并发量,但是系统中的一些稀缺资源,如数据库连接、CPU、内存、硬盘等,面对扩容前后的请求是固定不变的,所以要进行限流,也就是根据实际的承载能力(并发的数据库连接/系统计算能力等等指标:比如200个/秒 速率进行业务受理),限制并发处理的请求量,限流常使用漏桶算法,而漏桶算法常用RateLimiter进行实现。

行业场景:①秒杀系统中的:限流(漏桶算法),②电信行业月初+月末业务量高峰期

漏桶算法原理

桶的上游请求数量,不进行任何限制(顶多给个桶的上沿),而桶的下游始终是固定的,比如1秒中固定处理200个请求,超出(上沿)的请求,一种处理方式为降级,即告诉客户端请求超限,稍后重试,另一种方式可以写入数据库, 然后告诉客户端请求会稍后处理。这样,并发处理的业务量最多是200个请求。

漏桶算法实现

桶:Bucket.java

package com.mzj.guava.concurrent.ratelimiter.bucket;

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

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;

public class Bucket {

    //桶
    private final ConcurrentLinkedQueue<Integer> container = new ConcurrentLinkedQueue<>();

    //桶的上沿
    private final static int BUCKET_LIMIT = 1000;//最多能向桶里放入1000个请求

    //桶的下沿
    private final RateLimiter limiter = RateLimiter.create(10);//做多1秒只能处理10个请求

    //是否允许放入桶的Monitor
    private final Monitor offerMonitor = new Monitor();
    //是否允许从桶中获取元素
    private final Monitor pollMonitor = new Monitor();

    //桶的上游向桶中放数据
    public void submit(Integer data) {
        if (offerMonitor.enterIf(offerMonitor.newGuard(() -> container.size() < BUCKET_LIMIT))) {
            try {
                container.offer(data);
                System.out.println(Thread.currentThread() + " submit data " + data + ",current size : " + container.size());
            } finally {
                offerMonitor.leave();
            }
        } else {
            //超过桶的上沿,则降级
            throw new IllegalStateException("The bucket is full.");
        }
    }

    public void takeTheConsumer(Consumer<Integer> consumer) {
        if (pollMonitor.enterIf(pollMonitor.newGuard(() -> !container.isEmpty()))) {
            try {
                System.out.println(Thread.currentThread() + " waiting " + limiter.acquire());
                consumer.accept(container.poll());
            } finally {
                pollMonitor.leave();
            }
        }
    }
}

测试代码:BucketTest.java

package com.mzj.guava.concurrent.ratelimiter.bucket;

import org.omg.CORBA.DATA_CONVERSION;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class BucketTest {
    public static void main(String[] args) {
        final Bucket bucket = new Bucket();
        final AtomicInteger DATA_CREATOR = new AtomicInteger(0);

        //生产者线程:5个线程向桶中提交数据(每个线程1秒提交5次请求,总计25个请求)
        IntStream.range(0,5).forEach(i -> {
            new Thread(() ->{
                for (;;){
                    int data = DATA_CREATOR.getAndIncrement();
                    bucket.submit(data);
                    try{
                        TimeUnit.MILLISECONDS.sleep(200L);
                    }catch (Exception e){
                        if(e instanceof IllegalStateException){
                            System.out.println(e.getMessage());
                        }
                    }
                }
            }).start();
        });

        //消费者线程
        //生产者线程:也是5个线程从桶中拿请求进行处理,但是多少个线程不重要,因为限制为每秒只能处理10个
        IntStream.range(0,5).forEach(i -> {
            new Thread(() ->{
                for (;;){
                    bucket.takeTheConsumer(x-> System.out.println(Thread.currentThread()+ " W " + x));
                }
            }).start();
        });
    }
}

实际开发中关键点

请求溢出时的降级方法,目的是用户体验要好,比如:及时通知客户端,稍后处理,或者提示目前系统繁忙,请稍后再试

代码示例

https://github.com/mazhongjia/googleguava/tree/master/src/main/java/com/mzj/guava/concurrent/ratelimiter/bucket

2.2 使用RateLimiter实现令牌桶算法

引入:漏桶算法不关心往桶里放入的请求数量是多少,而关注的是处理请求时控制在一种匀速的速率进行处理,比如100个/s

而令牌桶与漏桶是相反的,令牌桶关注(限制)的是放入桶的速率。

但是需要注意:rateLimiter获取令牌,仅仅控制了调用rateLimiter.tryAcquire后进入下面方法体的速率(不管几个线程),而并未同步进入方法体的线程,所以如果设计多线程访问共享资源,要另外进行同步处理!!!

代码示例

https://github.com/mazhongjia/googleguava/tree/master/src/main/java/com/mzj/guava/concurrent/ratelimiter/tokenBucket

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值