20.线程系列- 高并发中常见的限流方式(并发控制,漏桶,令牌桶(RateLimiter))

本文内容

  1. 介绍常见的限流算法
  2. 通过控制最大并发数来进行限流
  3. 通过漏桶算法来进行限流
  4. 通过令牌桶算法来进行限流
  5. 限流工具类RateLimiter

常见的限流算法

  1. 通过控制最大并发数来进行限流
  2. 通过漏桶算法来进行限流
  3. 通过令牌桶算法来进行限流

通过控制最大并发数来进行限流

以秒杀业务为例,100万人抢购,100万人同时发起请求,最终能抢到的人也就是前面几个人,后面的基本上都没有希望。那么我们可以通过控制并发数来实现,比如并发控制在10个,其他超过并发数的请求全部拒绝,提示秒杀失败,请稍后重试。

并发控制的,通俗解释:一大波人去商场购物,必须经过一个门口,门口有个门卫,兜里有指定数量的门禁卡,来的人先去门卫那边拿门禁卡,拿到卡的人才能刷卡进入。进去的人出来之后会把卡归还给门卫,门卫可以把归还来的卡继续发放给其他顾客使用。

JUC中提供了这样的工具类,Semaphore。

public class Demo1 {

    static Semaphore semaphore = new Semaphore(5);

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                boolean flag = false;
                try {
                    flag = semaphore.tryAcquire(100, TimeUnit.MICROSECONDS);
                    if (flag) {
                        //休眠2秒,模拟下单操作
                        System.out.println(Thread.currentThread() + ",尝试下单中。。。。。");
                        TimeUnit.SECONDS.sleep(2);
                    } else {
                        System.out.println(Thread.currentThread() + ",秒杀失败,请稍微重试!");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (flag) {
                        semaphore.release();
                    }
                }
            }).start();
        }
    }

}

输出:

Thread[Thread-10,5,main],尝试下单中。。。。。
Thread[Thread-8,5,main],尝试下单中。。。。。
Thread[Thread-9,5,main],尝试下单中。。。。。
Thread[Thread-12,5,main],尝试下单中。。。。。
Thread[Thread-11,5,main],尝试下单中。。。。。
Thread[Thread-2,5,main],秒杀失败,请稍微重试!
Thread[Thread-1,5,main],秒杀失败,请稍微重试!
Thread[Thread-18,5,main],秒杀失败,请稍微重试!
Thread[Thread-16,5,main],秒杀失败,请稍微重试!
Thread[Thread-0,5,main],秒杀失败,请稍微重试!
Thread[Thread-3,5,main],秒杀失败,请稍微重试!
Thread[Thread-14,5,main],秒杀失败,请稍微重试!
Thread[Thread-6,5,main],秒杀失败,请稍微重试!
Thread[Thread-13,5,main],秒杀失败,请稍微重试!
Thread[Thread-17,5,main],秒杀失败,请稍微重试!
Thread[Thread-7,5,main],秒杀失败,请稍微重试!
Thread[Thread-19,5,main],秒杀失败,请稍微重试!
Thread[Thread-15,5,main],秒杀失败,请稍微重试!
Thread[Thread-4,5,main],秒杀失败,请稍微重试!
Thread[Thread-5,5,main],秒杀失败,请稍微重试!

使用漏桶算法来进行限流

国庆期间比较火爆的景点,人流量巨大,一般入口处会有限流的弯道,让游客进去进行排序,排在前面的人,每隔一段时间会放一波进入景区。排队人数超过了指定的限制,后面再来的人会被告知今天已经游客量达到峰值,会被拒绝排队,让其明天或者以后再来,这种玩法采用漏桶限流的方式。

漏桶算法思路很简单,水先进入到漏桶中,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

示例如下:

public class demo2 {

    static class BucketLimit{
        static AtomicInteger threadNum = new AtomicInteger(1);
        //容量
        private int capcity;
        //流速
        private int flowRate;
        //流速时间单位
        private TimeUnit flowRateUnit;
        //漏桶流出的任务时间间隔(纳秒)
        private long flowRateNanosTime;
        //队列
        private BlockingQueue<Node> queue;
        //漏桶中存放的元素
        class Node {
            private Thread thread;

            public Node(Thread thread) {
                this.thread = thread;
            }
        }

        public BucketLimit(int capcity, int flowRate, TimeUnit flowRateUnit) {
            this.capcity = capcity;
            this.flowRate = flowRate;
            this.flowRateUnit = flowRateUnit;
            this.bucketThreadWork();
        }
        //漏桶线程
        public void bucketThreadWork() {
            this.queue = new ArrayBlockingQueue<Node>(capcity);
            //漏桶流出的任务时间间隔(纳秒)
            this.flowRateNanosTime = flowRateUnit.toNanos(1) / flowRate;
            Thread thread = new Thread(this::bucketWork);
            thread.setName("漏桶线程-" + threadNum.getAndIncrement());
            thread.start();
        }
        //漏桶线程开始工作
        public void bucketWork() {
            while (true) {
                //从队列中移除node
                Node node = this.queue.poll();
                if (Objects.nonNull(node)) {
                    //唤醒任务线程
                    LockSupport.unpark(node.thread);
                }
                //休眠flowRateNanosTime
                LockSupport.parkNanos(this.flowRateNanosTime);
            }
        }
        //返回一个漏桶
        public static BucketLimit build(int capcity, int flowRate, TimeUnit flowRateUnit) {
            if (capcity < 0 || flowRate < 0) {
                throw new IllegalArgumentException("capcity、flowRate必须大于0!");
            }
            return new BucketLimit(capcity, flowRate, flowRateUnit);
        }
        //当前线程加入漏桶,返回false,表示漏桶已满;
        // true:表示被漏桶限流成功,可以继续处理任务
        public boolean acquire() {
            Thread thread = Thread.currentThread();
            Node node = new Node(thread);
            if (this.queue.offer(node)) {
                LockSupport.park();
                return true;
            }
            return false;
        }
    }
    public static void main(String[] args) {
        BucketLimit bucketLimit = BucketLimit.build(10, 60, TimeUnit.MINUTES);
        for (int i = 0; i < 15; i++) {
            new Thread(() -> {
                boolean acquire = bucketLimit.acquire();
                System.out.println(System.currentTimeMillis() + " " + acquire);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

输出:

1608538375003 false
1608538375003 false
1608538375003 false
1608538375003 false
1608538375003 false
1608538375999 true
1608538377000 true
1608538378000 true
1608538379000 true
1608538380001 true
1608538381001 true
1608538382001 true
1608538383001 true
1608538384002 true
1608538385002 true

代码中BucketLimit.build(10, 60, TimeUnit.MINUTES);创建了一个容量为10,流水为60/分钟的漏桶。

使用令牌桶算法来进行限流

令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。这种算法可以应对突发程度的请求,因此比漏桶算法好。

限流工具类RateLimiter

Google开源工具包Guava提供了限流工具类RateLimiter,可以非常方便的控制系统每秒吞吐量,示例代码如下:

public class demo3 {

    public static void main(String[] args) throws InterruptedException {
        RateLimiter rateLimiter = RateLimiter.create(5);//设置QPS为5
        for (int i = 0; i < 10; i++) {
            rateLimiter.acquire();
            System.out.println(System.currentTimeMillis());
        }
        System.out.println("----------");
        //可以随时调整速率,我们将qps调整为10
        rateLimiter.setRate(10);
        for (int i = 0; i < 10; i++) {
            rateLimiter.acquire();
            System.out.println(System.currentTimeMillis());
        }
    }
}

输出:

1608539111331
1608539111539
1608539111730
1608539111931
1608539112130
1608539112331
1608539112530
1608539112731
1608539112930
1608539113130
----------
1608539113330
1608539113430
1608539113530
1608539113630
1608539113731
1608539113830
1608539113931
1608539114031
1608539114130
1608539114230
Disconnected from the target VM, address: '127.0.0.1:61858', transport: 'socket'

Process finished with exit code 0

代码中RateLimiter.create(5)创建QPS为5的限流对象,后面又调用rateLimiter.setRate(10);将速率设为10,输出中分2段,第一段每次输出相隔200毫秒,第二段每次输出相隔100毫秒,可以非常精准的控制系统的QPS。

上面介绍的这些,业务中可能会用到,也可以用来应对面试。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值