java限流(漏桶+令牌桶)实现

限流介绍

​ 限流算法基本上有三种:漏桶、令牌桶和信号量这三种,本文是关于漏桶和令牌桶的限流的实现方式.

限流注意的点是多线程下限流算法的修改并发问题
一、令牌桶
1.话不多说,上代码
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
public class TokenBucket {
    // 上一次令牌发放时间
    public static long lastTime = 0;
    // 桶的容量
    public static int capacity = 1;
    // 令牌生成速度 /s
    public static int rate = 1;
    // 当前令牌数量
    public static AtomicLong tokens = new AtomicLong(0);


    //返回值说明:
    // false 没有被限制到
    // true 被限流
    public static synchronized boolean isLimited(int applyCount) {
        long now = System.currentTimeMillis();
        //时间间隔,单位为 ms
        long gap = now - lastTime;
        //场景三:当前请求和上次请求,在同一个时间区间
        //当前时间,在时间区间之内
        //以时间区间为计算维度,同一个区间,没有必要重复去计算令牌数量
        if (lastTime != 0 && gap < 1000) {
            if (tokens.get() < applyCount) {
                // 若拿不到令牌,则拒绝
                return true;
            } else {
                // 还有令牌,领取令牌
                tokens.getAndAdd(-applyCount);
                return false;
            }
        }
        log.info("overGap gap = " + gap);
        if (lastTime == 0) {
            gap = 1000;
        }
        //计算时间段内的令牌数
        int reverse_permits = (int) (gap * rate / 1000);
        int all_permits = Math.toIntExact(tokens.get() + reverse_permits);
        // 当前令牌数
        tokens.set(Math.min(capacity, all_permits));
        log.info("tokens {} capacity {} gap {} ", tokens, capacity, gap);
        lastTime = now;
        if (tokens.get() < applyCount) {
            // 若拿不到令牌,则拒绝
            return true;
        } else {
            // 还有令牌,领取令牌
            tokens.getAndAdd(-applyCount);
            return false;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 2; i++) {
            Thread.sleep(200);
            CompletableFuture.runAsync(() -> {
                int x = 0;
                for (int j = 0; j < 5; j++) {
                    System.out.println("Request " + x++ + ": " + (isLimited(1) ? "Rejected" : "Accepted"));
                }
            });
        }
        Thread.sleep(1500);
    }
}

二、漏桶
1.话不多说,上代码
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicLong;

@Slf4j
public class LeakBucketLimiter {
    // 计算的起始时间
    private static long lastOutTime = System.currentTimeMillis();

    // 时间区间的时间间隔 ms
    private static long interval = 1000;

    // 流出速率 每秒 2 次
    private static int leakRate = 1;

    // 桶的容量
    private static int capacity = 1;

    //剩余的水量
    private static AtomicLong waterInBucket = new AtomicLong(0);

    //返回值说明:
    // false 没有被限制到
    // true 被限流
    public static synchronized boolean isLimit() {
        // 如果是空桶,就当前时间作为漏出的时间
        if (waterInBucket.get() == 0) {
            System.out.println("汉字666");
            lastOutTime = System.currentTimeMillis();
            waterInBucket.addAndGet(1);
            return false;
        }
        //场景三:当前请求和上次请求,在同一个时间区间
        long nowTime = System.currentTimeMillis();
        //当前时间,在时间区间之内
        //漏水以时间区间为计算维度,同一个区间,没有必要重复去计算漏水
        if (nowTime < lastOutTime + interval) {
            // 尝试加水,并且水还未满 ,放行
            if ((waterInBucket.get()) < capacity) {
                waterInBucket.addAndGet(1);
                return false;
            } else {
                // 水满,拒绝加水, 限流
                return true;
            }
        }
        //场景二: 桶里边有水
        //当前时间,在时间区间之外
        // 计算漏水,以时间的区间为维度的
        int waterLeaked = ((int) ((System.currentTimeMillis() - lastOutTime) / 1000)) * leakRate;
        // 计算剩余水量
        int waterLeft = Math.toIntExact(waterInBucket.get() - waterLeaked);
        //校正数据
        waterLeft = Math.max(0, waterLeft);
        waterInBucket.set(waterLeft);
        // 重新更新leakTimeStamp
        lastOutTime = System.currentTimeMillis();
        // 尝试加水,并且水还未满 ,放行
        if ((waterInBucket.get()) < capacity) {
            waterInBucket.addAndGet(1);
            return false;
        } else {
            // 水满,拒绝加水, 限流
            return true;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 3; i++) {
            int x = 0;
            for (int j = 0; j < 5; j++) {
                System.out.println((System.currentTimeMillis()-start)+" Request " + x++ + ": " + (isLimit() ? "Rejected" : "Accepted"));
                Thread.sleep(200);
            }
        }
    }

}
三、写在最后

​ 1.在spring全局过滤器中使用限流算法可以有效的限制单位时间的访问应用的流量

​ 2.这个demo只是针对单个应用的所有请求的限流,如果要针对到每个人或者每个session的话,实现思路有:可以为每个用户创建一个限流器,然后保存到本地缓存中(可能有更好的实现方式)

​ 3.如果是分布式的应用集群,可以将用户的登录信息保存到redis当中,使用redis+lua脚本的方式或者在nginx+lua的方式进行限流操作(没做过,应该有坑)

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
令牌桶算法是一种常见的限流算法,它可以控制请求的速率,防止系统被过多的请求压垮。下面是Java实现令牌桶算法的步骤和代码逻辑: 1. 定义一个令牌桶类,包含以下属性: - 最后一次令牌发放时间 - 桶的容量 - 令牌生成速度 - 当前令牌数量 2. 实现一个获取令牌的方法,该方法会在每次请求到来时被调用,具体实现如下: - 计算当前令牌数量 - 判断当前令牌数量是否足够 - 如果令牌数量不足,则拒绝请求 - 如果令牌数量足够,则领取令牌,并执行业务逻辑 3. 使用ScheduledExecutorService定时生成令牌,具体实现如下: - 每隔一段时间生成一定数量的令牌 - 如果令牌数量超过桶的容量,则不再生成令牌 下面是Java实现令牌桶算法的代码逻辑: ``` @Slf4j public class TokensLimiter { private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); // 最后一次令牌发放时间 public long timeStamp = System.currentTimeMillis(); // 桶的容量 public int capacity = 10; // 令牌生成速度10/s public int rate = 10; // 当前令牌数量 public int tokens; public void acquire() { scheduledExecutorService.scheduleWithFixedDelay(() -> { long now = System.currentTimeMillis(); // 当前令牌数 tokens = Math.min(capacity, (int) (tokens + (now - timeStamp) * rate / 1000)); // 每隔0.5秒发送随机数量的请求 int permits = (int) (Math.random() * 9) + 1; log.info("请求令牌数:" + permits + ",当前令牌数:" + tokens); timeStamp = now; if (tokens < permits) { // 若不到令牌,拒绝 log.info("限流了"); } else { // 还有令牌,领取令牌 tokens -= permits; log.info("剩余令牌=" + tokens); } }, 1000, 500, TimeUnit.MILLISECONDS); } public static void main(String[] args) { TokensLimiter tokensLimiter = new TokensLimiter(); tokensLimiter.acquire(); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值