Java基础、中级、高级、架构面试资料

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

JAVA herman 3076浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog2,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云

今天,抽空,我给大家介绍一下限流。目前关于限流的框架和工具都比较多,比如 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 等再集群入口处进行限流,这部分后面我抽出时间再写文章来扩展吧。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!

本文原文出处:业余草: » 电商系统中的秒杀高并发单机限流实战