JAVA应用层限流

Posted l_learning

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA应用层限流相关的知识,希望对你有一定的参考价值。

限流就是对请求或并发数进行限制;通过对一个时间窗口内的请求量进行限制来保障系统的正常运行。如果我们的服务资源有限、处理能力有限,就需要对调用我们服务的上游请求进行限制,以防止自身服务由于资源耗尽而停止服务。
如:公交车满载拒载、地铁站限流排队等

限流中提到的阈值拒绝策略两个概念
阈值:在一个单位时间内允许的请求量。如 QPS 限制为10,说明 1 秒内最多接受 10 次请求。
拒绝策略:超过阈值的请求的拒绝策略,常见的拒绝策略有直接拒绝、排队等待等。

1. 计数器算法

一种简单方便的限流算法。通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数。

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 计数限流器
 */
public class CountLimiter 

    // 阈值
    private Integer qps;
    // 时间窗口(毫秒)
    private long timeWindows = 1000;
    // 计数器
    private AtomicInteger requestCount = new AtomicInteger();

    private long startTime = System.currentTimeMillis();

    private CountLimiter()

    public CountLimiter(Integer qps) 
        this.qps = qps;
    

    public synchronized boolean tryAcquire() 
        if ((System.currentTimeMillis() - startTime) > timeWindows) 
            requestCount.set(0);
            startTime = System.currentTimeMillis();
        
        return requestCount.incrementAndGet() <= qps;
    


测试

public class RateLimiterTest 

    public static void main(String[] args) throws InterruptedException 
        CountLimiter countLimiter = new CountLimiter(2);
        for (int i = 0; i < 10; i++) 
            Thread.sleep(200);
            LocalTime now = LocalTime.now();
            if (!countLimiter.tryAcquire()) 
                System.out.println(now + ": 限流 ");
             else 
                System.out.println(now + ": 通行 ");
            
        
    

2. 滑动计数器算法

滑动计数器算法为解决计数器算法遇到时间窗口的临界突变时,会发生qps突增,如 1s 中的后 500 ms 和第 2s 的前 500ms 时,虽然是加起来是 1s 时间,却可以被请求 2 次以上。

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 滑动计数器限流
 */
public class SlidingCountLimiter  

    /**
     * 阈值
     */
    private int qps = 2;

    /**
     * 时间窗口大小(毫秒)
     */
    private long windowSize = 1000;

    /**
     * 子窗口数量
     */
    private Integer windowCount = 10;

    /**
     * 窗口列表
     */
    private WindowInfo[] windowArray = new WindowInfo[windowCount];

    private SlidingCountLimiter() 

    public SlidingCountLimiter(int qps) 
        this.qps = qps;
        long currentTimeMillis = System.currentTimeMillis();
        for (int i = 0; i < windowArray.length; i++) 
            windowArray[i] = new WindowInfo(currentTimeMillis, new AtomicInteger(0));
        
    

    /**
     * 获取权限
     * @return
     */
    public synchronized boolean tryAcquire() 
        long currentTimeMillis = System.currentTimeMillis();
        // 当前时间窗口 = 当前时间 % 时间窗口大小 / (时间窗口大小 / 子窗口数量)
        int currentIndex = (int)(currentTimeMillis % windowSize / (windowSize / windowCount));
        // 更新当前窗口计数 & 重置过期窗口计数
        int sum = 0;
        for (int i = 0; i < windowArray.length; i++) 
            WindowInfo windowInfo = windowArray[i];
            if ((currentTimeMillis - windowInfo.getTime()) > windowSize) 
                windowInfo.getNumber().set(0);
                windowInfo.setTime(currentTimeMillis);
            
            if (currentIndex == i && windowInfo.getNumber().get() < qps) 
                windowInfo.getNumber().incrementAndGet();
            
            sum = sum + windowInfo.getNumber().get();
        
        // 当前 QPS 是否超过限制
        return sum <= qps;
    

    private class WindowInfo 
        // 窗口开始时间
        private Long time;
        // 计数器
        private AtomicInteger number;

        public WindowInfo(long time, AtomicInteger number) 
            this.time = time;
            this.number = number;
        

        public Long getTime() 
            return time;
        

        public void setTime(Long time) 
            this.time = time;
        

        public AtomicInteger getNumber() 
            return number;
        

        public void setNumber(AtomicInteger number) 
            this.number = number;
        
    


测试

public class RateLimiterTest 

    public static void main(String[] args) throws InterruptedException 
		SlidingCountLimiter slidingCountLimiter = new SlidingCountLimiter(2);
        for (int i = 0; i < 20; i++) 
            Thread.sleep(200);
            if (slidingCountLimiter.tryAcquire()) 
                System.out.print(LocalTime.now() + ": 通行 ");
             else 
                System.out.println(LocalTime.now() + ": 限流 ");
            
        
    
    

3. 令牌桶算法 (推荐)

每个请求都需要去桶中拿取一个令牌,取到令牌则继续执行;如果桶中无令牌可取,就触发拒绝策略,可以是超时等待,也可以是直接拒绝本次请求,由此达到限流目的。

使用Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter

<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>28.2-jre</version>
</dependency>
public class RateLimiterTest 

    public static void main(String[] args) throws InterruptedException 
		RateLimiter rateLimiter = RateLimiter.create(2);
        for (int i = 0; i < 10; i++) 
            String time = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME);
            System.out.println(time + ":" + rateLimiter.tryAcquire());
            Thread.sleep(200);
        
    
    

以上是关于JAVA应用层限流的主要内容,如果未能解决你的问题,请参考以下文章

JAVA应用层限流

JAVA应用层限流

「算法数据结构专题」带你认识常用的限流算法的技术指南

Java并发编程- 应用限流及其常见算法

Java高并发系统限流算法的应用

java 基于Redis实现分布式应用限流