高并发系统防止流量击穿
场景:访问请求流量过大而导致服务器宕机或击穿
限流是对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机。常用的限流算法有令牌桶和和漏桶,而Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流
- 缓存:缓存的目的是提升系统访问速度和增大系统处理容量
- 降级:降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行
- 限流:限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理
我们经常在调别人的接口的时候会发现有限制,比如微信公众平台接口、百度API Store、聚合API等等这样的,对方会限制每天最多调多少次或者每分钟最多调多少次
我们自己在开发系统的时候也需要考虑到这些,比如我们公司在上传商品的时候就做了限流,因为用户每一次上传商品,我们需要将商品数据同到到美团、饿了么、京东、百度、自营等第三方平台,这个工作量是巨大,频繁操作会拖慢系统,故做限流。
以上都是题外话,接下来我们重点看一下令牌桶算法
令牌桶算法
下面是从网上找的两张图来描述令牌桶算法:
RateLimiter
https://github.com/google/guava

RateLimiter的代码不长,注释加代码432行,看一下RateLimiter怎么用
 1 package com.cjs.example;
 2 
 3 import com.google.common.util.concurrent.RateLimiter;
 4 import org.springframework.web.bind.annotation.RequestMapping;
 5 import org.springframework.web.bind.annotation.RestController;
 6 
 7 import java.text.SimpleDateFormat;
 8 import java.util.Date;
 9 
10 @RestController
11 public class HelloController {
12 
13     private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
14 
15     private static final RateLimiter rateLimiter = RateLimiter.create(2);
16 
17     /**
18      * tryAcquire尝试获取permit,默认超时时间是0,意思是拿不到就立即返回false
19      */
20     @RequestMapping("/sayHello")
21     public String sayHello() {
22         if (rateLimiter.tryAcquire()) { //  一次拿1个
23             System.out.println(sdf.format(new Date()));
24             try {
25                 Thread.sleep(500);
26             } catch (InterruptedException e) {
27                 e.printStackTrace();
28             }
29         }else {
30             System.out.println("limit");
31         }
32         return "hello";
33     }
34 
35     /**
36      * acquire拿不到就等待,拿到为止
37      */
38     @RequestMapping("/sayHi")
39     public String sayHi() {
40         rateLimiter.acquire(5); //  一次拿5个
41         System.out.println(sdf.format(new Date()));
42         return "hi";
43     }
44 
45 }




关于RateLimiter:
- A rate limiter。每个acquire()方法如果必要的话会阻塞直到一个permit可用,然后消费它。获得permit以后不需要释放。
- RateLimiter在并发环境下使用是安全的:它将限制所有线程调用的总速率。注意,它不保证公平调用。
- RateLimiter在并发环境下使用是安全的:它将限制所有线程调用的总速率。注意,它不保证公平调用。Rate limiter(直译为:速度限制器)经常被用来限制一些物理或者逻辑资源的访问速率。这和java.util.concurrent.Semaphore正好形成对照。
- 一个RateLimiter主要定义了发放permits的速率。如果没有额外的配置,permits将以固定的速度分配,单位是每秒多少permits。默认情况下,Permits将会被稳定的平缓的发放。
- 可以配置一个RateLimiter有一个预热期,在此期间permits的发放速度每秒稳步增长直到到达稳定的速率
基本用法:
final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second"
void submitTasks(List<Runnable> tasks, Executor executor) {
    for (Runnable task : tasks) {
        rateLimiter.acquire(); // may wait
        executor.execute(task);
    }
}
实现

SmoothBursty以稳定的速度生成permit
SmoothWarmingUp是渐进式的生成,最终达到最大值趋于稳定
源码片段解读:
public abstract class RateLimiter {
    /**
     * 用给定的吞吐量(“permits per second”)创建一个RateLimiter。
     * 通常是QPS
     */
    public static RateLimiter create(double permitsPerSecond) {
        return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
    }
    
    static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
        RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
        rateLimiter.setRate(permitsPerSecond);
        return rateLimiter;
    }
    
    /**
     * 用给定的吞吐量(QPS)和一个预热期创建一个RateLimiter
     */
    public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
        checkArgument(warmupPeriod >= 0, "warmupPeriod must not be negative: %s", warmupPeriod);
        return create(permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer());
    }
    static RateLimiter create(
            double permitsPerSecond,
            long warmupPeriod,
            TimeUnit unit,
            double coldFactor,
            SleepingStopwatch stopwatch) {
        RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
        rateLimiter.setRate(permitsPerSecond);
        return rateLimiter;
    }
    private final SleepingStopwatch stopwatch;
    //    锁
    private volatile Object mutexDoNotUseDirectly;
    private Object mutex() {
        Object mutex = mutexDoNotUseDirectly;
        if (mutex == null) {
            synchronized (this) {
                mutex = mutexDoNotUseDirectly;
                if (mutex == null) {
                    mutexDoNotUseDirectly = mutex = new Object();
                }
            }
        }
        return mutex;
    }
    
    /**
     * 从RateLimiter中获取一个permit,阻塞直到请求可以获得为止
     * @return 休眠的时间,单位是秒,如果没有被限制则是0.0
     */
    public double acquire() {
        return acquire(1);
    }
  
    /**
     * 从RateLimiter中获取指定数量的permits,阻塞直到请求可以获得为止
     */
    public double acquire(int permits) {
        long microsToWait = reserve(permits);
        stopwatch.sleepMicrosUninterruptibly(microsToWait);
        return 1.0 * microsToWait / SECONDS.toMicros(1L);
    }
    
    /**
     * 预定给定数量的permits以备将来使用
     * 直到这些预定数量的permits可以被消费则返回逝去的微秒数
     */
    final long reserve(int permits) {
        checkPermits(permits);
        synchronized (mutex()) {
            return reserveAndGetWaitLength(permits, stopwatch.readMicros());
        }
    }
    
    private static void checkPermits(int permits) {
        checkArgument(permits > 0, "Requested permits (%s) must be positive", permits);
    }
    
    final long reserveAndGetWaitLength(int permits, long nowMicros) {
        long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
        return max(momentAvailable - nowMicros, 0);
    }
}
abstract class SmoothRateLimiter extends RateLimiter {
    
    /** The currently stored permits. */
    double storedPermits;
    /** The maximum number of stored permits. */
    double maxPermits;
    /**
     * The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits
     * per second has a stable interval of 200ms.
     */
    double stableIntervalMicros;
    
    /**
     * The time when the next request (no matter its size) will be granted. After granting a request,
     * this is pushed further in the future. Large requests push this further than small requests.
     */
    private long nextFreeTicketMicros = 0L; // could be either in the past or future
    
    final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
        resync(nowMicros);
        long returnValue = nextFreeTicketMicros;
        double storedPermitsToSpend = min(requiredPermits, this.storedPermits);    //    本次可以获取到的permit数量
        double freshPermits = requiredPermits - storedPermitsToSpend;    //    差值,如果存储的permit大于本次需要的permit数量则此处是0,否则是一个正数
        long waitMicros =
            storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
                + (long) (freshPermits * stableIntervalMicros);    //    计算需要等待的时间(微秒)
        this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
        this.storedPermits -= storedPermitsToSpend;    //    减去本次消费的permit数
        return returnValue;
    }
    
    void resync(long nowMicros) {
        // if nextFreeTicket is in the past, resync to now
        if (nowMicros > nextFreeTicketMicros) {    //    表示当前可以获得permit
            double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();    //    计算这段时间可以生成多少个permit
            storedPermits = min(maxPermits, storedPermits + newPermits);    //    如果超过maxPermit,则取maxPermit,否则取存储的permit+新生成的permit
            nextFreeTicketMicros = nowMicros;    //    设置下一次可以获得permit的时间点为当前时间
        }
    }
}
RateLimiter实现的令牌桶算法,不仅可以应对正常流量的限速,而且可以处理突发暴增的请求,实现平滑限流。
转自:https://www.cnblogs.com/cjsblog/p/9379516.html
    Keep moving forwards~
 
                    
                     
                    
                 
                    
                
 
   


 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号