高并发中常见的限流方式

本文详细介绍了在高并发场景下,如何通过Semaphore实现并发数控制,以及漏桶算法和令牌桶算法进行流量限流,展示了使用JUC的Semaphore和Guava的RateLimiter库进行限流操作的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

一群人去动物园参观,为了避免拥挤限制人数,管理员兜里面有指定数量的门禁卡,来的人先去管理员那边拿取门禁卡,拿到卡的人才可以刷卡进入动物园,拿不到的可以继续等待。进去的人出来之后会把卡归还给管理员,管理员可以把归还来的卡继续发放给其他排队的顾客使用。

本例使用JUC包的Semaphore来做并发数控制。可以将最大并发数控制在指定数量内。

package cn.demo.other;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 使用信号量做秒杀限流,没能获得信号量的请求直接报失败
 */
public class SemaphoreSeckill {

    /**
     * 信号量,共5个,表示同一时间只有5个线程能获取到许可
     */
    static Semaphore semaphore = new Semaphore(5);

    public static void main(String[] args) {
        //使用20个线程一起模拟下单
        for (int i = 0; i < 20; i++) {
            new Thread(SemaphoreSeckill::order).start();
        }
    }

    /**
     * 下单
     */
    public static void order() {
        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();
            }
        }
    }
}

日志打印

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

2. 通过漏桶算法来进行限流

漏桶算法,就是将水(请求)以不限速的方式放到漏桶里,如果漏桶满了则将多余的请求溢出(丢弃),漏桶下方会匀速取出一滴滴水(请求),以达到限流的目的。

请求进入漏桶后,会进入等待状态;

请求流出漏桶后,会被唤醒进入运行状态。

漏桶算法示意图
漏桶算法示意图
自定义漏桶算法工具如下:

package cn.demo.other;

import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

/**
 * 漏桶算法限流demo
 */
public class BucketLimitDemo {

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

        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 = 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;
        }

        //漏桶中存放的元素
        class Node {
            private Thread thread;

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

    public static void main(String[] args) throws InterruptedException {
        BucketLimit bucketLimit = BucketLimit.build(10, 60, TimeUnit.MINUTES);
        TimeUnit.SECONDS.sleep(2);
        for (int i = 0; i < 15; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread() + "获取令牌...");
                boolean acquire = bucketLimit.acquire();
                System.out.println(Thread.currentThread() + " " + acquire);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

}

3. 通过令牌桶算法来进行限流

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。如图2所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

3.1 限流工具类RateLimiter

package cn.demo.other;

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

/**
 * 使用guava中的限流工具RateLimiter
 */
public class RateLimiterDemo {

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

日志:

1618481805066
1618481805566
1618481806062
1618481806562
1618481807063
1618481807562
1618481808062
1618481808563
1618481809062
1618481809563
----------
1618481810062
1618481810230
1618481810396
1618481810563
1618481810729
1618481810895
1618481811063
1618481811229
1618481811396
1618481811562
1618481811729
1618481811896
1618481812062
1618481812229
1618481812396
1618481812563
1618481812729
1618481812896
1618481813062
1618481813230

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值