限流常见方案,值得一看
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达

- 一、限流思路 -
1、熔断
2、服务降级
相同点: 目标一致 都是从可用性和可靠性出发,为了防止系统崩溃; 用户体验类似,最终都让用户体验到的是某些功能暂时不可用。 不同点: 触发原因不同,服务熔断一般是某个服务(下游服务,即被调用的服务)故障引起; 而服务降级一般是从整体负荷考虑。 

- 二、限流算法 -
1、计数器算法

package com.todaytalents.rcn.parser.util;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * 计数器实现限流:
 * 每分钟只允许100个请求,第一个请求进去的时间为startTime,在startTime + 60s内只允许100个请求
 * 60s内超过100个请求后,则拒绝请求,
 * 不超过,允许请求,到第60s 重新设置时间。
 *
 * @author: Arafat
 * @date: 2021/12/29
 * @company: 澳B99999
 **/
public class CalculatorCurrentLimiting {
    /**
     * 限流个数
     */
    private int maxCount = 100;
    /**
     * 指定的时间内:秒
     */
    private long specifiedTime = 60;
    /**
     * 原子类计数器
     */
    private AtomicInteger atomicInteger = new AtomicInteger(0);
    /**
     * 起始时间
     */
    private long startTime = System.currentTimeMillis();
    /**
     * @param maxCount      限流个数
     * @param specifiedTime 指定的时间内
     * @return 返回true 不限流,返回false 则限流
     */
    public boolean limit(int maxCount, int specifiedTime) {
        atomicInteger.addAndGet(1);
        if (1 == atomicInteger.get()) {
            startTime = System.currentTimeMillis();
            atomicInteger.addAndGet(1);
            return true;
        }
        // 超过时间间隔,重新开始计数
        if (System.currentTimeMillis() - startTime > specifiedTime * 1000) {
            startTime = System.currentTimeMillis();
            atomicInteger.set(1);
            return true;
        }
        // 还在时间间隔内,检查是否超过限流数量
        if (maxCount < atomicInteger.get()) {
            return false;
        }
        return true;
    }
}
2、滑动窗口

3、漏桶算法

import java.util.concurrent.atomic.AtomicInteger;
/**
 * 漏桶算法:把水滴看成请求
 *
 * @author: Arafat
 * @date: 2021/12/29
 **/
public class LeakyBucket {
    /**
     * 桶的容量
     */
    private int capacity = 100;
    /**
     * 桶剩余的水滴的量(初始化的时候桶为空)
     */
    private AtomicInteger water = new AtomicInteger(0);
    /**
     * 水滴的流出的速率 每1000毫秒流出1滴
     */
    private int leakRate;
    /**
     * 第一次请求之后,木桶在这个时间点开始漏水
     */
    private long leakTimeStamp;
    public LeakyBucket(int leakRate) {
        this.leakRate = leakRate;
    }
    public boolean acquire() {
        // 如果是空桶,就用当前时间作为桶开始漏出的时间
        if (water.get() == 0) {
            leakTimeStamp = System.currentTimeMillis();
            water.addAndGet(1);
            return capacity == 0 ? false : true;
        }
        // 先执行漏水,计算剩余水量
        int waterLeft = water.get() - ((int) ((System.currentTimeMillis() - leakTimeStamp) / 1000)) * leakRate;
        water.set(Math.max(0, waterLeft));
        // 重新更新leakTimeStamp
        leakTimeStamp = System.currentTimeMillis();
        // 尝试加水,并且水还未满
        if ((water.get()) < capacity) {
            water.addAndGet(1);
            return true;
        } else {
            // 水满,拒绝加水,直接溢出
            return false;
        }
    }
}/**
 * @author Arafat
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/test")
public class TestController {
    /**
     * 漏桶:水滴的漏出速率是每秒 1 滴
     */
    private LeakyBucket leakyBucket = new LeakyBucket(1);
    private UserService userService;
    /**
     * 漏桶限流
     *
     * @return
     */
    @RequestMapping("/searchUserInfoByLeakyBucket")
    public Object searchUserInfoByLeakyBucket() {
        // 限流判断
        boolean acquire = leakyBucket.acquire();
        if (!acquire) {
            log.info("请您稍后再试!");
            return Reply.success("请您稍后再试!");
        }
        // 若没有达到限流的要求,直接调用接口查询
        return Reply.success(userService.search());
    }
}削峰:有大量流量进入时,会发生溢出,从而限流保护服务可用。 缓冲:不至于直接请求到服务器,缓冲压力,消费速度固定,因为计算性能固定。 
4、令牌桶算法

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * 令牌桶算法限流
 *
 * @author: Arafat
 * @date: 2021/12/30
 **/
public class TokensLimiter {
    /**
     * 最后一次令牌发放时间
     */
    public long timeStamp = System.currentTimeMillis();
    /**
     * 桶的容量
     */
    public int capacity = 10;
    /**
     * 令牌生成速度10/s
     */
    public int rate = 10;
    /**
     * 当前令牌数量
     */
    public int tokens ;
    /**
     * 周期性线程池
     */
    private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
    /**
     * 线程池每0.5s发送随机数量的请求,
     * 每次请求计算当前的令牌数量,
     * 请求令牌数量超出当前令牌数量,则限流。
     */
    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;
            System.out.println("请求令牌数:" + permits + ",当前令牌数:" + tokens);
            timeStamp = now;
            if (tokens < permits) {
                // 若不到令牌,则拒绝
                System.out.println("限流了");
            } else {
                // 还有令牌,领取令牌
                tokens -= permits;
                System.out.println("剩余令牌=" + tokens);;
            }
        }, 1000, 500, TimeUnit.MILLISECONDS);
    }
    public static void main(String[] args) {
        TokensLimiter tokensLimiter = new TokensLimiter();
        tokensLimiter.acquire();
    }
}漏桶与令牌桶算法的区别
主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。 在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。 令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。 具体情况具体分析,只有最合适的算法,没有最优的算法。 
import com.google.common.util.concurrent.RateLimiter;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
 * @author Arafat
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/test")
public class TestController {
    /**
     * 每秒钟放入n个令牌,相当于每秒只允许执行n个请求
     * n = 1
     * n == 5
     */
    //private static final RateLimiter RATE_LIMITER = RateLimiter.create(1);
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(5);
    public static void main(String[] args) {
        // 每秒中限制1个请求  0:表示等待超时时间,设置0表示不等待,直接拒绝请求
        boolean tryAcquire = RATE_LIMITER.tryAcquire(0, TimeUnit.SECONDS);
        // false表示没有获取到token
        if (!tryAcquire) {
            System.out.println("现在抢购的人数过多,请稍等一下下哦!");
        }
        // tryAcquire 模拟有20个请求
        for (int i = 0; i < 20; i++) {
            /**
             * 尝试从令牌桶中获取令牌,
             * 若获取不到则等待300毫秒看能不能获取到
             */
            boolean request = RATE_LIMITER.tryAcquire(300, TimeUnit.MILLISECONDS);
            if (request) {
                // 获取成功,执行相应逻辑
                handle(i);
            }
        }
        // acquire 模拟有20个请求
        for (int i = 0; i < 20; i++) {
            // 从令牌桶中获取一个令牌,若没有获取到会阻塞直到获取到为止,所以所有的请求都会被执行
            RATE_LIMITER.acquire();
            // 获取成功,执行相应逻辑
            handle(i);
        }
    }
    private static void handle(int i) {
        System.out.println("第 " + i + " 次请求OK~~~");
    }
}
- 三、集群限流 -
redisOperations.opsForValue().increment()

作者 | 涛姐涛哥
来源 |  cnblogs.com/taojietaoge/p/15744243.html
加锋哥微信: java1239 围观锋哥朋友圈,每天推送Java干货! 
评论

