关于java:高并发优雅的做限流

40次阅读

共计 3045 个字符,预计需要花费 8 分钟才能阅读完成。

技术剖析

如果你比拟关注当初的技术模式,就会晓得微服务当初火的一塌糊涂,当然,事物都有两面性,微服务也不是解决技术,架构等问题的万能钥匙。如果服务化带来的利大于弊,菜菜还是举荐将零碎服务化。随着服务化的过程的一直演变,各种概念以及技术随之而来。任何一种计划都是为了解决问题而存在。比方:熔断设计,接口幂等性设计,重试机制设计,还有明天菜菜要说的限流设计,等等这些技术简直都充斥在每个零碎中。

就明天来说的限流,书面意思和作用统一,就是为了限度,通过对并发拜访或者申请进行限速或者一个工夫窗口内的申请进行限速来爱护零碎。一旦达到了限度的临界点,能够用拒绝服务、排队、或者期待的形式来爱护现有零碎,不至于产生雪崩景象。

限流就像做帝都的地铁个别,如果你住在西二旗或者天通苑兴许会领会的更粗浅一些。我更习惯在技术角度用消费者的角度来论述,须要限流的个别起因是消费者能力无限,目标为了防止超过消费者能力而呈现系统故障。当然也有其余相似的状况也能够用限流来解决。

限流的表现形式上大部分能够分为两大类:

  1. 限度消费者数量。也能够说生产的最大能力值。比方:数据库的连接池是偏重的是总的连接数。还有菜菜以前写的线程池,实质上也是限度了消费者的最大生产能力。
  2. 能够被生产的申请数量。这里的数量能够是刹时并发数,也能够是一段时间内的总并发数。菜菜明天要帮 YY 妹子做的也是这个。

除此之外,限流还有别的表现形式,例如依照网络流量来限流,依照 cpu 使用率来限流等。依照限流的范畴又能够分为分布式限流,利用限流,接口限流等。无论怎么变动,限流都能够用以下图来示意:

罕用技术实现

令牌桶算法

令牌桶是一个寄存固定容量令牌的桶,依照固定速率往桶里增加令牌,填满了就抛弃令牌,申请是否被解决要看桶中令牌是否足够,当令牌数减为零时则回绝新的申请。令牌桶容许肯定水平突发流量,只有有令牌就能够解决,反对一次拿多个令牌。令牌桶中装的是令牌。

漏桶算法

漏桶一个固定容量的漏桶,依照固定常量速率流出申请,流入申请速率任意,当流入的申请数累积到漏桶容量时,则新流入的申请被回绝。漏桶能够看做是一个具备固定容量、固定流出速率的队列,漏桶限度的是申请的流出速率。漏桶中装的是申请。

计数器

有时咱们还会应用计数器来进行限流,次要用来限度肯定工夫内的总并发数,比方数据库连接池、线程池、秒杀的并发数;计数器限流只有肯定工夫内的总申请数超过设定的阀值则进行限流,是一种简略粗犷的总数量限流,而不是均匀速率限流。

除此之外,其实依据不同的业务场景,还能够呈现很多不同的限流算法,然而总的规定只有一条:只有合乎以后业务场景的限流策略就是最好的

限流的其余基础知识请百度!!

另一种形式解决妹子问题

回归问题,YY 妹子的问题,菜菜不筹备用以上所说的几种算法来帮忙她。菜菜筹备用一个依照时间段限度申请总数的形式来限流。总体思路是这样:

  1. 用一个环形来代表通过的申请容器。
  2. 用一个指针指向以后申请所到的地位索引,来判断以后申请工夫和以后地位上次申请的时间差,依此来判断是否被限度。
  3. 如果申请通过,则以后指针向前挪动一个地位,不通过则不挪动地位
  4. 反复以上步骤 直到永远 …….

用代码谈话才是王道

以下代码不改或者略微批改可用于生产环境

以下代码的外围思路是这样的:指针以后地位的工夫元素和以后工夫的差来决定是否容许此次申请,这样通过的申请在工夫上体现的比拟平滑。

思路远比语言重要,任何语言也可为之,请 phper,golanger,javaer 自行实现一遍即可

 // 限流组件, 采纳数组做为一个环
    class LimitService
    {
        // 以后指针的地位
        int currentIndex = 0;
        // 限度的工夫的秒数,即:x 秒容许多少申请
        int limitTimeSencond = 1;
        // 申请环的容器数组
        DateTime?[] requestRing = null;
        // 容器扭转或者挪动指针时候的锁
        object objLock = new object();

        public LimitService(int countPerSecond,int  _limitTimeSencond)
        {requestRing = new DateTime?[countPerSecond];
            limitTimeSencond= _limitTimeSencond;
        }

        // 程序是否能够持续
        public bool IsContinue()
        {lock (objLock)
            {var currentNode = requestRing[currentIndex];
                // 如果以后节点的值加上设置的秒 超过以后工夫,阐明超过限度
                if (currentNode != null&& currentNode.Value.AddSeconds(limitTimeSencond) >DateTime.Now)
                {return false;}
                // 以后节点设置为以后工夫
                requestRing[currentIndex] = DateTime.Now;
                // 指针挪动一个地位
                MoveNextIndex(ref currentIndex);
            }            
            return true;
        }
        // 扭转每秒能够通过的申请数
        public bool ChangeCountPerSecond(int countPerSecond)
        {lock (objLock)
            {requestRing = new DateTime?[countPerSecond];
                currentIndex = 0;
            }
            return true;
        }

        // 指针往前挪动一个地位
        private void MoveNextIndex(ref int currentIndex)
        {if (currentIndex != requestRing.Length - 1)
            {currentIndex = currentIndex + 1;}
            else
            {currentIndex = 0;}
        }
    }

测试程序如下:

 static  LimitService l = new LimitService(1000, 1);
        static void Main(string[] args)
        {
            int threadCount = 50;
            while (threadCount >= 0)
            {
                Thread t = new Thread(s =>
                {Limit();
                });
                t.Start();
                threadCount--;
            }           

            Console.Read();}

        static void Limit()
        {
            int i = 0;
            int okCount = 0;
            int noCount = 0;
            Stopwatch w = new Stopwatch();
            w.Start();
            while (i < 1000000)
            {var ret = l.IsContinue();
                if (ret)
                {okCount++;}
                else
                {noCount++;}
                i++;
            }
            w.Stop();
            Console.WriteLine($"共用{w.ElapsedMilliseconds}, 容许:{okCount},  拦挡:{noCount}");
        }

测试后果如下:

最大用时 15 秒, 共解决申请 1000000*50=50000000 次
并未产生 GC 操作,内存使用率非常低,每秒解决 300 万次 + 申请。以上程序修改为 10 个线程,大概用时 4 秒之内

如果是强劲的服务器或者线程数较少状况下处理速度将会更快

写在最初

以上代码尽管简略,然而却为限流的外围代码(其实还有优化余地),通过其余封装能够实用于 Webapi 的 filter 或其余场景。妹子问题解决了,要不要让她请我吃个饭呢?

更多精彩文章

  • 分布式大并发系列
  • 架构设计系列
  • 趣学算法和数据结构系列
  • 设计模式系列

正文完
 0