程序员修神之路--高并发优雅的做限流(有福利)


菜菜哥,有时间吗?


YY妹,什么事?


我最近的任务是做个小的秒杀活动,我怕把后端接口压垮,X总说这可关系到公司的存亡


简单呀,你就做个限流呗


这个没做过呀,菜菜哥,帮妹子写一个呗,事成了,以后有什么要求随便说


那好呀,先把我工资涨一下


那算了,我找别人帮忙吧


跟你开玩笑呢,给哥2个小时时间


谢谢菜菜哥,以后你什么要求我都答应你


好嘞,年轻人就是豪爽

◆◆
技术分析
◆◆

如果你比较关注现在的技术形式,就会知道微服务现在火的一塌糊涂,当然,事物都有两面性,微服务也不是解决技术,架构等问题的万能钥匙。如果服务化带来的利大于弊,菜菜还是推荐将系统服务化。随着服务化的进程的不断演化,各种概念以及技术随之而来。任何一种方案都是为了解决问题而存在。比如:熔断设计,接口幂等性设计,重试机制设计,还有今天菜菜要说的限流设计,等等这些技术几乎都充斥在每个系统中。

就今天来说的限流,书面意思和作用一致,就是为了限制,通过对并发访问或者请求进行限速或者一个时间窗口内的请求进行限速来保护系统。一旦达到了限制的临界点,可以用拒绝服务、排队、或者等待的方式来保护现有系统,不至于发生雪崩现象。

限流就像做帝都的地铁一般,如果你住在西二旗或者天通苑也许会体会的更深刻一些。我更习惯在技术角度用消费者的角度来阐述,需要限流的一般原因是消费者能力有限,目的为了避免超过消费者能力而出现系统故障。当然也有其他类似的情况也可以用限流来解决。

限流的表现形式上大部分可以分为两大类:

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或其他场景。

妹子问题解决了,要不要让她请我吃个饭呢?


号外号外:菜菜的福利来啦
这是菜菜一直以来的一个愿望,希望给菜菜的支持者一些福利,福利并不大,却是菜菜的一片心意。今天菜菜自掏腰包,请各位注意查收(关注公众号可快人一步)~

一大波福利正在接近

原文地址:https://www.cnblogs.com/zhanlang/p/10424757.html

时间: 2024-08-07 16:01:52

程序员修神之路--高并发优雅的做限流(有福利)的相关文章

程序员修神之路--高并发下为什么更喜欢进程内缓存

菜菜哥,告诉你一个好消息 YY妹子,什么好消息,你有男票了? 不是啦,我做的一个网站,以前经常由于访问量太大而崩溃,现在我加上了缓存,很稳定啦 加的什么缓存呢? 我用的redis,号称业界最快的缓存组件了 你觉得现在的缓存操作应该是最快的了吗? 是的,我觉得没有缓存能比这种模式更快了 你先停停,我给你先讲个故事 进程内缓存是指缓存和应用程序在相同地址空间.即同一个进程内.分布式缓存是指缓存和应用程序位于不同进程的缓存,通常部署在不同服务器上. 从前有个机构,机构的主人叫做 CPU,这个机构专门派

程序员修神之路--做好分库分表其实很难之一(继续送书)

菜哥,领导让我开发新系统了 这么说领导对你还是挺信任的呀~ 必须的,为了设计好这个新系统,数据库设计我花了好多心思呢 做一个系统我觉得不应该从数据库入手,应该从设计业务模型开始,先不说这个,说说你的数据库设计的优势 为了高性能我首先设计了分库 分表策略,为以后打下基础 那你的数据量将来会很大吗?分库分表其实涉及到很多难题,你了解过吗? 我觉得分库分表很容易呀 是吗? 是否需要分 说到数据库分库分表,不能一味的追求,我们要明白为什么要进行分库分表才是最终目的.现在网上一些人鼓吹分库分表如何应对了多

程序员修神之路--redis做分布式锁可能不那么简单

菜哥,复联四上映了,要不要一起去看看? 又想骗我电影票,对不对? 呵呵,想去看了叫我呀 看来你工作不饱和呀 哪有,这两天我刚基于redis写了一个分布式锁,很简单 不管你基于什么做分布式锁,你觉得很简单吗?来来来 在计算机世界里,对于锁大家并不陌生,在现代所有的语言中几乎都提供了语言级别锁的实现,为什么我们的程序有时候会这么依赖锁呢?这个问题还是要从计算机的发展说起,随着计算机硬件的不断升级,多核cpu,多线程,多通道等技术把计算机的计算速度大幅度提升,原来同一时间只能执行一条cpu指令的时代已

程序员修神之路--为什么有了SOA,我们还用微服务?

菜菜哥,我最近需要做一个项目,老大让我用微服务的方式来做 那挺好呀,微服务现在的确很流行 我以前在别的公司都是以SOA的方式,SOA也是面向服务的方式呀 的确,微服务和SOA有相同之处 面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来.接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台.操作系统和编程语言.这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互.它是一种设计方法,其中包

高并发场景下的限流策略

高并发场景下的限流策略: 在开发高并发系统时,有很多手段来保护系统:缓存.降级.限流. 当访问量快速增长.服务可能会出现一些问题(响应超时),或者会存在非核心服务影响到核心流程的性能时, 仍然需要保证服务的可用性,即便是有损服务.所以意味着我们在设计服务的时候,需要一些手段或者关键数据进行自动降级,或者配置人工降级的开关. 缓存的目的是提升系统访问速度和增大系统处理的容量,可以说是抗高并发流量的银弹:降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉某些功能,等高峰或者问题解决后再打开:

慌了,大厂最后一面居然问我高并发系统下的限流?

开涛大神在博客中说过:在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.本文结合作者的一些经验介绍限流的相关概念.算法和常规的实现方式. 缓存 缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪.使用缓存不单单能够提升系统访问速度.提高并发访问量,也是保护数据库.保护系统的有效方式.大型网站一般主要是"读",缓存的使用很容易被想到.在大型"写"系统中,缓存也常常扮演者非常重要的角色.比如累积一些数据批量写入,内存里面的缓存

程序员修仙之路- CXO让我做一个计算器!!

菜菜呀,个税最近改革了,我得重新计算你的工资呀,我需要个计算器,你开发一个吧 CEO,CTO,CFO于一身的CXO X总,咱不会买一个吗? 菜菜 那不得花钱吗,一块钱也是钱呀··这个计算器支持加减乘除运算就行,很简单 CEO,CTO,CFO于一身的CXO (尼玛)那能不能给我涨点工资呀? 菜菜 公司现在很困难,你这个计算器关系到公司的存亡,你要注意呀!! CEO,CTO,CFO于一身的CXO (关于撇开话题佩服的五体投地)好吧X总,我尽快做 菜菜 给你一天时间,我这里着急要用 CEO,CTO,C

程序员修仙之路--优雅快速的统计千万级别uv

菜菜,咱们网站现在有多少PV和UV了? Y总,咱们没有统计pv和uv的系统,预估大约有一千万uv吧 写一个统计uv和pv的系统吧 网上有现成的,直接接入一个不行吗? 别人的不太放心,毕竟自己写的,自己拥有主动权.给你两天时间,系统性能不要太差呀 好吧~~~ 定义PV是page view的缩写,即页面浏览量,通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标.网页浏览数是评价网站流量最常用的指标之一,简称为PV UV是unique visitor的简写,是指通过互联网访问.浏览这个网页的自

程序员跳槽神级攻略

2015的5月已经过半了,3.4月份的求职高峰已经过去了,你换工作了吗? 这次我们想聊的,就是程序员跳槽这件事儿,我打算从三个方面来说: 什么时候该跳槽 跳槽前你需要做的准备工作 到哪里找跳槽机会 什么时候该跳槽 我在"自我发现,找到适合自己的职位"一文中提供了"周末探视法"让大家分析自己对当前工作的感觉.这个方法很简单,你只需做下面这件事儿: 在周日的晚上,想着明天要上班了,记录自己此刻的念头和心情. OK.就是这样.如果你内查到犹豫.恐慌.紧张.担忧.抗拒之类的