17 高并发之限流

作者: 十丈_红尘 | 来源:发表于2018-11-03 14:46 被阅读183次

    郑重说明:尽管网络上有很多的资源可以借鉴,但是笔者还是需要很多的帮助才能写出这些总结笔记
    1️⃣首先也是最重要的慕课网给了我巨大的帮助,自从第一次打开慕课网以后,从此就在我心里播下了一颗学习的种子;
    2️⃣要特别鸣谢慕课网的Jimin老师,从他那里我"偷了"许多的想法放到这篇笔记里边;
    3️⃣本文内容直接出自Jimin老师的课程Java并发编程与高并发解决方案
    4️⃣清醒时做事,糊涂时学习,祝大家都能梦想成真;


    1️⃣概念

    限流就是通过对并发访问/请求进行限速或一个时间窗口内的请求进行限速,从而达到保护系统的目的。一般系统可以通过压测来预估能处理的峰值,一旦达到设定的峰值阀值,则可以拒绝服务(定向错误页或告知资源没有了)、排队或等待(例如:秒杀、评论、下单)、降级(返回默认数据)
    限流不能乱用,否则正常流量会出现一些奇怪的问题,从而导致用户抱怨。


    2️⃣限流场景模拟

    假设有130W到140W的数据插入到数据库中,如果没有做限流,数据库的主库会突然接收到130w的插入操作
    首先是网络上的开销,很可能直接把带宽占满,导致其他请求无法正常传输和处理,其次会是数据库的负载突然增高,导致无法处理某些数据库的操作,也有可能数据库没有足够的连接导致某些数据库插入查询失败;
    还有一点就是现在数据库都做了主从设计,主数据库的数据还要同步给从库,这时瞬间插入了大量的数据,会带来从库和主库的延迟特别大,这时从库查询不准确的概率也会跟着提升。
    如果我们放慢插入数据库的速度,这时插入数据库主库的速率会很正常,同步到从库也很正常。网络消耗也可以接收不会影响其他服务。


    3️⃣应用限流算法

    ① 计数器法
    有时我们还会使用计数器来进行限流,主要用来限制一定时间内的总并发数,比如数据库连接池、线程池、秒杀的并发数;计数器限流只要一定时间内的总请求数超过设定的阀值则进行限流,是一种简单粗暴的总数量限流,而不是平均速率限流。

    这个方法有一个致命问题:临界问题——当遇到恶意请求,在0:59时,瞬间请求100次,并且在1:00请求100次,那么这个用户在1秒内请求了200次,用户可以在重置节点突发请求,而瞬间超过我们设置的速率限制,用户可能通过算法漏洞击垮我们的应用。

    ② 滑动窗口

    在上图中,整个红色矩形框是一个时间窗口,在我们的例子中,一个时间窗口就是1分钟,然后我们将时间窗口进行划分,如上图我们把滑动窗口
    划分为6格,所以每一格代表10秒,每超过10秒,我们的时间窗口就会向右滑动一格,每一格都有自己独立的计数器,例如:一个请求在0:35到达,
    那么0:30到0:39的计数器会+1,那么滑动窗口是怎么解决临界点的问题呢?如上图,0:59到达的100个请求会在灰色区域格子中,而1:00到达的请求
    会在红色格子中,窗口会向右滑动一格,那么此时间窗口内的总请求数共200个,超过了限定的100,所以此时能够检测出来触发了限流。
    回头看看计数器算法,会发现,其实计数器算法就是窗口滑动算法,只不过计数器算法没有对时间窗口进行划分,所以是一格。
    由此可见,当滑动窗口的格子划分越多,限流的统计就会越精确。

    ③ 漏桶算法

    这个算法很简单。首先,我们有一个固定容量的桶,有水进来,也有水出去。对于流进来的水,我们无法预计共有多少水流进来,也无法预计流水速度,但
    对于流出去的水来说,这个桶可以固定水流的速率,而且当桶满的时候,多余的水会溢出来。

    ④ 令牌桶算法

    从上图中可以看出,令牌算法有点复杂,桶里存放着令牌token。桶一开始是空的,token以固定的速率r往桶里面填充,直到达到桶的容量,多余的token会
    被丢弃。每当一个请求过来时,就会尝试着移除一个token,如果没有token,请求无法通过。

    4️⃣限流算法代码实现
    1 单机限流
    // 令牌桶算法实现 tryAcquire
    @Slf4j
    public class RateLimiterExample1 {
    
        private static RateLimiter rateLimiter = RateLimiter.create(5);
    
        public static void main(String[] args) throws Exception {
    
            for (int index = 0; index < 100; index++) {
                if (rateLimiter.tryAcquire(190, TimeUnit.MILLISECONDS)) {
                    handle(index);
                }
            }
        }
    
        private static void handle(int i) {
           log.info("{}", i);
        }
    }
    
    // 令牌桶算法实现 acquire
    @Slf4j
    public class RateLimiterExample2 {
    
        private static RateLimiter rateLimiter = RateLimiter.create(5);
    
        public static void main(String[] args) throws Exception {
    
            for (int index = 0; index < 100; index++) {
                rateLimiter.acquire();
                handle(index);
            }
        }
    
        private static void handle(int i) {
           log.info("{}", i);
        }
    }
    
    2 分布式限流

    建议使用Redis实现,也可以根据总限流数实现;

    相关文章

      网友评论

        本文标题:17 高并发之限流

        本文链接:https://www.haomeiwen.com/subject/pgukxqtx.html