电商系统中的秒杀高并发单机限流实战

今天,抽空,我给大家介绍一下限流。目前关于限流的框架和工具都比较多,比如 Redis、阿里的 Sentinel、Nginx、OpenResty 等。今天我先给大家介绍一个简单的限流,单机限流方法。

电商系统中的秒杀高并发单机限流实战
高并发限流

限流不管是在生活中还是代码中都很常用,比如上图中的某景区黄金周限流。

限流,顾名思义就是在规定的时间内限制流通量。在项目代码中就是限制单位时间内请求的并发数,确保服务的可靠性。一般限流、缓存、降级是处理高并发的常见 3 种手段,也是程序员必知必会的必备生存技能。

限流的使用场景太多了,多的数不过来。比如:秒杀限流,导出报表限流等。如果按资源分类,限制 MQ 消费、CPU、内存、磁盘使用率、网络连接数、网络流量等。而我们今天要讲的是,限制接口的并发调用数。

限流基本上有两个主要且非常经典的算法:漏桶算法和令牌桶算法。

电商系统中的秒杀高并发单机限流实战
漏桶算法示意图

漏桶算法就像一个漏斗一样,进来的水量就好像访问流量一样,而出去的水量就像是我们的系统处理请求一样。当访问流量过大时,这个漏斗中就会积水,如果水太多了就会溢出,直接响应超时或者系统繁忙等情况。

漏桶算法的实现往往依赖于队列,请求到达时,如果队列未满则直接放入队列,如果满了则溢出。然后有一个处理器按照固定频率从队列头取出请求进行处理。

根据生活中的现象以及上图中描述的出水速率恒定。因此漏桶算法的一个重要特征或缺点是,它的输出速率是恒定的,这也是区别于令牌桶的根本。

电商系统中的秒杀高并发单机限流实战
令牌桶算法示意图

令牌桶算法则是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝。当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直接处理,并且令牌桶删除一个令牌。如果获取不到,该请求就要被限流,要么直接丢弃,要么在缓冲区等待。

令牌桶算法和漏桶算法的主要区别如下:

  • 漏桶是出,令牌是进
  • 令牌是允许伸缩的

漏桶算法不能够有效地使用网络资源,因为漏桶的漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性的流量。通常,漏桶算法与令牌桶算法结合起来为网络流量提供更高效的控制。

这两个算法的具体实现,我就不列举代码了。我们今天直接来使用 Guava 这个开眼框架来实现限流,这是一个很好的工具类框架,如果你把它的源码完整的看一遍,我相信你的技能会有很大的提升。

Guava 的 RateLimiter 实现令牌桶算法:SmoothBursty 实现了平滑突发限流、SmoothWarmingUp 实现了平滑预热限流。

RateLimiter 采用工厂方法模式,根据调用参数的不同,生成 两个不同的 RateLimiter 子类:SmoothBursty 和 SmoothWarmingUp。

static RateLimiter create(SleepingStopwatch stopwatch, 
    double permitsPerSecond) {
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 
    1.0 /* maxBurstSeconds */);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
}
static RateLimiter create(
    SleepingStopwatch stopwatch,
    double permitsPerSecond,
    long warmupPeriod,
    TimeUnit unit,
    double coldFactor) {
    RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, 
    warmupPeriod, unit, coldFactor);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
}

RateLimiter 类是一个线程安全的类,大家可以放心的使用。下面看一下我的单机脱敏后的秒杀简化代码。

public static void main(String[] args) {
    //0.5代表一秒最多多少个
    RateLimiter rateLimiter = RateLimiter.create(0.5);
    List<Runnable> tasks = new ArrayList<Runnable>();
    for (int i = 0; i < 10; i++) {
        tasks.add(new SeckillRequest(i));
    }
    ExecutorService threadPool = Executors.newCachedThreadPool();
    for (Runnable runnable : tasks) {
        System.out.println("等待时间:" + rateLimiter.acquire());
        threadPool.execute(runnable);
    }
}
private static class SeckillRequest implements Runnable {
    private int id;
    public SeckillRequest(int id) {
        this.id = id;
    }
    public void run() {
        System.out.println(id);
    }
}

SmoothWarmingUp 创建方式要使用这个函数:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)。其中 permitsPerSecond 参数表示每秒新增的令牌数,warmupPeriod 参数表示在从冷启动速率过渡到平均速率的时间间隔。

RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);
for(int i = 1; i < 5;i++) {
    System.out.println(limiter.acquire());
}
Thread.sleep(1000L);
for(int i = 1; i < 5;i++) {
    System.out.println(limiter.acquire());
}

SmoothWarmingUp 的速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节 warmupPeriod 参数实现一开始就是平滑固定速率。

上面我介绍的方法都是单机限流,或者说是应用级别的限流,现在假设将应用部署到多台机器,应用级限流方式只是单应用内的请求限流,不能进行全局限流。因此我们需要分布式限流和接入层限流来解决这个问题。

分布式限流,一般需要借助第三方系统来实现。比如:Redis、Sentinel。也或者 Nginx、OpenResty 等再集群入口处进行限流,这部分后面我抽出时间再写文章来扩展吧。

电商系统中的秒杀高并发单机限流实战

: » 电商系统中的秒杀高并发单机限流实战

原创文章,作者:6024010,如若转载,请注明出处:https://blog.ytso.com/252111.html

(0)
上一篇 2022年5月4日
下一篇 2022年5月4日

相关推荐

发表回复

登录后才能评论