前言

俗话说的好,冰冻三尺非一日之寒,滴水穿石非一日之功,罗马也不是一天就建成的。两周前秒杀案例初步成型,分享到了中国最大的同 性友网站-码云。同时也收到了不少小伙伴的建议和投诉。我从不认为分布式、集群、秒杀这些就应该是大厂的专利,在互联网的今天无论什么时候都要时刻武装自己,只有这样,也许你的春天就在明天。

在开发秒杀系统案例的过程中,前面主要分享了队列、缓存、锁和分布式锁以及静态化等等。缓存的目的是为了提升系统访问速度和增强系统的处理能力;分布式锁解决了集群下数据的安全一致性问题;静态化无疑是减轻了缓存以及DB层的压力。

限流

然而再牛逼的机器,再优化的设计,对于特殊场景我们也是要特殊处理的。就拿秒杀来说,可能会有百万级别的用户进行抢购,而商品数量远远小于用户数量。如果这些请求都进入队列或者查询缓存,对于最终结果没有任何意义,徒增后台华丽的数据。对此,为了减少资源浪费,减轻后端压力,我们还需要对秒杀进行限流,只需保障部分用户服务正常即可。

就秒杀接口来说,当访问频率或者并发请求超过其承受范围的时候,这时候我们就要考虑限流来保证接口的可用性,以防止非预期的请求对系统压力过大而引起的系统瘫痪。通常的策略就是拒绝多余的访问,或者让多余的访问排队等待服务。

限流算法

任何限流都不是漫无目的的,也不是一个开关就可以解决的问题,常用的限流算法有:令牌桶,漏桶。

令牌桶

令牌桶算法是网络流量整 形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送(百科)。

在秒杀活动中,用户的请求速率是不固定的,这里我们假定为10r/s,令牌按照5个每秒的速率放入令牌桶,桶中最多存放20个令牌。仔细想想,是不是总有那么一部分请求被丢弃。

漏桶

漏桶算法的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被×××以便为网络提供一个稳定的流量(百科)。

令牌桶是无论你流入速率多大,我都按照既定的速率去处理,如果桶满则拒绝服务。

应用限流

Tomcat

在Tomcat容器中,我们可以通过自定义线程池,配置最大连接数,请求处理队列等参数来达到限流的目的(图片源自网络)。

Tomcat默认使用自带的连接池,这里我们也可以自定义实现,打开/conf/server.xml文件,在Connector之前配置一个线程池:

<Executor name="tomcatThreadPool"namePrefix="tomcatThreadPool-"maxThreads="1000"maxIdleTime="300000"minSpareThreads="200"/>
  • name:共享线程池的名字。这是Connector为了共享线程池要引用的名字,该名字必须唯一。默认值:None;
  • namePrefix:在JVM上,每个运行线程都可以有一个name 字符串。这一属性为线程池中每个线程的name字符串设置了一个前缀,Tomcat将把线程号追加到这一前缀的后面。默认值:tomcat-exec-;
  • maxThreads:该线程池可以容纳的最大线程数。默认值:200;
  • maxIdleTime:在Tomcat关闭一个空闲线程之前,允许空闲线程持续的时间(以毫秒为单位)。只有当前活跃的线程数大于minSpareThread的值,才会关闭空闲线程。默认值:60000(一分钟)。
  • minSpareThreads:Tomcat应该始终打开的最小不活跃线程数。默认值:25。
配置Connector
<Connector executor="tomcatThreadPool"port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443"minProcessors="5"maxProcessors="75"acceptCount="1000"/>
  • executor:表示使用该参数值对应的线程池;
  • minProcessors:服务器启动时创建的处理请求的线程数;
  • maxProcessors:最大可以创建的处理请求的线程数;
  • acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。

API限流

秒杀活动中,接口的请求量会是平时的数百倍甚至数千倍,从而有可能导致接口不可用,并引发连锁反应导致整个系统崩溃,甚至有可能会影响到其它服务。

那么如何应对这种突然事件呢?这里我们采用开源工具包guava提供的限流工具类RateLimiter进行API限流,该类基于"令牌桶算法",开箱即用。

自定义定义注解

/*** 自定义注解  限流*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface ServiceLimit {String description()  default "";
}

自定义切面

/*** 限流 AOP*/
@Component
@Scope
@Aspect
public class LimitAspect {//每秒只发出100个令牌,此处是单进程服务的限流,内部采用令牌捅算法实现private static   RateLimiter rateLimiter = RateLimiter.create(100.0);//Service层切点  限流@Pointcut("@annotation(com.itstyle.seckill.common.aop.ServiceLimit)")  public void ServiceAspect() {}@Around("ServiceAspect()")public  Object around(ProceedingJoinPoint joinPoint) { Boolean flag = rateLimiter.tryAcquire();Object obj = null;try {if(flag){obj = joinPoint.proceed();}} catch (Throwable e) {e.printStackTrace();}return obj;}
}

业务实现:

@Override
@ServiceLimit
@Transactional
public Result startSeckil(long seckillId, long userId) {//省略部分业务代码,详见秒杀源码
}

分布式限流

Nginx

如何使用Nginx实现基本的限流,比如单个IP限制每秒访问50次。通过Nginx限流模块,我们可以设置一旦并发连接数超过我们的设置,将返回503错误给客户端。

配置nginx.conf

#统一在http域中进行配置
#限制请求
limit_req_zone $binary_remote_addr $uri zone=api_read:20m rate=50r/s;
#按ip配置一个连接 zone
limit_conn_zone $binary_remote_addr zone=perip_conn:10m;
#按server配置一个连接 zone
limit_conn_zone $server_name zone=perserver_conn:100m;
server {listen       80;server_name  seckill.52itstyle.com;index index.jsp;location / {#请求限流排队通过 burst默认是0limit_req zone=api_read burst=5;#连接数限制,每个IP并发请求为2limit_conn perip_conn 2;#服务所限制的连接数(即限制了该server并发连接数量)limit_conn perserver_conn 1000;#连接限速limit_rate 100k;proxy_pass      http://seckill;}
}
upstream seckill {fair;server  172.16.1.120:8080 weight=1  max_fails=2 fail_timeout=30s;server  172.16.1.130:8080 weight=1  max_fails=2 fail_timeout=30s;
}

配置说明

imit_conn_zone

是针对每个IP定义一个存储session状态的容器。这个示例中定义了一个100m的容器,按照32bytes/session,可以处理3200000个session。

limit_rate 300k;

对每个连接限速300k. 注意,这里是对连接限速,而不是对IP限速。如果一个IP允许两个并发连接,那么这个IP就是限速limit_rate×2。

burst=5;

这相当于桶的大小,如果某个请求超过了系统处理速度,会被放入桶中,等待被处理。如果桶满了,那么抱歉,请求直接返回503,客户端得到一个服务器忙的响应。如果系统处理请求的速度比较慢,桶里的请求也不能一直待在里面,如果超过一定时间,也是会被直接退回,返回服务器忙的响应。

OpenResty

背影有没有很熟悉,对这就是那个直呼理解万岁老罗,2015年老罗在锤子科技T2发布会上将门票收入捐赠给了 OpenResty,也相信老罗是个有情怀的胖子。

这里我们使用 OpenResty 开源的限流方案,测试案例使用OpenResty1.13.6.1最新版本,自带lua-resty-limit-traffic模块以及案例 ,实现起来更为方便。

限制接口总并发数/请求数

秒杀活动中,由于突发流量暴增,有可能会影响整个系统的稳定性从而造成崩溃,这时候我们就要限制秒杀接口的总并发数/请求数。

这里我们采用 lua-resty-limit-traffic中的resty.limit.count模块实现,由于文章篇幅具体代码参见源码openresty/lua/limit_count.lua。

限制接口时间窗请求数

秒杀场景下,有时候并都是人肉鼠标,比如12306的抢票软件,软件刷票可比人肉鼠标快多了。此时我们就要对客户端单位时间内的请求数进行限制,以至于刷票不是那么猖獗。当然了道高一尺魔高一丈,抢票软件总是会有办法绕开你的防线,从另一方面讲也促进了技术的进步。

这里我们采用 lua-resty-limit-traffic中的resty.limit.conn模块实现,具体代码参见源码openresty/lua/limit_conn.lua。

平滑限制接口请求数

之前的限流方式允许突发流量,也就是说瞬时流量都会被允许。突然流量如果不加以限制会影响整个系统的稳定性,因此在秒杀场景中需要对请求整 形为平均速率处理,即20r/s。

这里我们采用 lua-resty-limit-traffic 中的resty.limit.req 模块实现漏桶限流和令牌桶限流。

其实漏桶和令牌桶根本的区别就是,如何处理超过请求速率的请求。漏桶会把请求放入队列中去等待均速处理,队列满则拒绝服务;令牌桶在桶容量允许的情况下直接处理这些突发请求。

漏桶

桶容量大于零,并且是延迟模式。如果桶没满,则进入请求队列以固定速率等待处理,否则请求被拒绝。

令牌桶

桶容量大于零,并且是非延迟模式。如果桶中存在令牌,则允许突发流量,否则请求被拒绝。

压测

为了测试以上配置效果,我们采用AB压测,Linux下执行以下命令即可:

# 安装
yum -y install httpd-tools
# 查看ab版本
ab -v
# 查看帮助
ab --help

测试命令:

ab -n 1000 -c 100 http://127.0.0.1/

测试结果:

Server Software:        openresty/1.13.6.1  #服务器软件
Server Hostname:        127.0.0.1     #IP
Server Port:            80            #请求端口号Document Path:          /             #文件路径
Document Length:        12 bytes      #页面字节数Concurrency Level:      100           #请求的并发数
Time taken for tests:   4.999 seconds #总访问时间
Complete requests:      1000          #总请求树
Failed requests:        0             #请求失败数量
Write errors:           0
Total transferred:      140000 bytes  #请求总数据大小
HTML transferred:       12000 bytes   #html页面实际总字节数
Requests per second:    200.06 [#/sec] (mean) #每秒多少请求,这个是非常重要的参数数值,服务器的吞吐量
Time per request:       499.857 [ms] (mean) #用户平均请求等待时间
Time per request:       4.999 [ms] (mean, across all concurrent requests)  # 服务器平均处理时间,也就是服务器吞吐量的倒数
Transfer rate:          27.35 [Kbytes/sec] received #每秒获取的数据长度Connection Times (ms)min  mean[+/-sd] median   max
Connect:        0    0   0.8      0       4
Processing:     5  474  89.1    500     501
Waiting:        2  474  89.2    500     501
Total:          9  475  88.4    500     501Percentage of the requests served within a certain time (ms)50%    50066%    50075%    50080%    50090%    50195%    50198%    50199%    501100%    501 (longest request)

源码:从0到1构建分布式秒杀系统

总结

以上限流方案,只是针对此次秒杀案例做一个简单的小结,大家也不要刻意区分那种方案的好坏,只要适合业务场景就是最好的。

参考

https://github.com/openresty/lua-resty-limit-traffic
https://blog.52itstyle.com/archives/1764/
https://blog.52itstyle.com/archives/775/

分享是快乐的,也见证了个人成长历程,文章大多都是工作经验总结以及平时学习积累,基于自身认知不足之处在所难免,也请大家指正,共同进步。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 如有问题, 可邮件(345849402@qq.com)咨询。

转载于:https://blog.51cto.com/itstyle/2127815

从构建分布式秒杀系统聊聊限流特技相关推荐

  1. 从构建分布式秒杀系统聊聊分布式锁

    从构建分布式秒杀系统聊聊分布式锁 1.案例介绍 在尝试了解分布式锁之前,大家可以想象一下,什么场景下会使用分布式锁? 单机应用架构中,秒杀案例使用ReentrantLcok或者synchronized ...

  2. 从构建分布式秒杀系统聊聊线程池

    前言 从0到1构建分布式秒杀系统案例的代码已经全部上传至码云,文章也被分发到各个平台.其中也收到了不少小伙伴喜欢和反馈,有网友如是说: 说实话,能用上的不多,中小企业都不可能用到,大型企业也不是一个人 ...

  3. 从构建分布式秒杀系统聊聊验证码

    2019独角兽企业重金招聘Python工程师标准>>> 前言 为了拦截大部分请求,秒杀案例前端引入了验证码.淘宝上很多人吐槽,等输入完秒杀活动结束了,对,结束了...... 当然了, ...

  4. 从构建分布式秒杀系统聊聊WebSocket推送通知 1

    前言 秒杀架构到后期,我们采用了消息队列的形式实现抢购逻辑,那么之前抛出过这样一个问题:消息队列异步处理完每个用户请求后,如何通知给相应用户秒杀成功? 场景映射 首先,我们举一个生活中比较常见的例子: ...

  5. 从构建分布式秒杀系统聊聊WebSocket推送通知

    前言 秒杀架构到后期,我们采用了消息队列的形式实现抢购逻辑,那么之前抛出过这样一个问题:消息队列异步处理完每个用户请求后,如何通知给相应用户秒杀成功? 场景映射 首先,我们举一个生活中比较常见的例子: ...

  6. disruptor框架为什么不流行_从构建分布式秒杀系统聊聊Disruptor高性能队列

    前言 秒杀架构持续优化中,基于自身认知不足之处在所难免,也请大家指正,共同进步.文章标题来自码友的建议,希望可以把阻塞队列ArrayBlockingQueue这个队列替换成Disruptor,由于之前 ...

  7. SpringBoot开发案例从0到1构建分布式秒杀系统

    前言 最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场景 ...

  8. 聊聊高并发系统之限流特技-1

    在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流. 缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹:而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉 ...

  9. 高并发系统之限流特技

    转载至:http://blog.csdn.net/g_hongjin/article/details/51649246 在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.缓存的目的是提升系统 ...

最新文章

  1. BorderDet(论文解读)
  2. 1-2-Active Directory 域服务准备概述
  3. 事务管理最佳实践多余的话之一“每次请求,一次数据库连接,一次事务”是不是金科玉律?...
  4. golang string 转换 uint64_Golang 的 #x27;print#x27; 源码解读
  5. MATLAB 转PDF图片过大导致两边缺失
  6. R语言对用电负荷时间序列数据进行K-medoids聚类建模和GAM回归
  7. iOS关键代码遭泄露;Google正式抛弃HTTP;微博网页版出故障 | 一周业界事
  8. 解决vue2+vue-cli3项目ie兼容问题
  9. OpenCV精进之路(十一):感兴趣区域ROI和logo添加技术
  10. html视图查看,视图.html · zhaohaihang/Semantic UI demo - Gitee.com
  11. Linux yum源码包安装和卸载
  12. Linux下硬盘分区详解
  13. echart 世界地图发光_echarts生成世界地图,百度echarts生成世界地图方法
  14. matlab 颜色代码 大全_空客 | 红、绿、蓝、白,各类颜色代码的含义
  15. 微信UnionId 部分开放
  16. 四种方法解决:Windows10下使用SVN文件夹不显示小绿勾
  17. ovftool导出虚拟机报错处理过程!
  18. ChatGPT|微信快速接入ChatGPT
  19. 第19章 项目收尾管理
  20. 大数据采集技术与预处理

热门文章

  1. 在64位的环境中使用VS建立Web项目进行Oracle连接需要注意WebDev是32位的
  2. NFS安装系统及PS1设定
  3. H264码流中SPS PPS
  4. nas挂载windows_在windows2012上挂载NAS文件系统
  5. 履带机器人运动控制器c++_敏捷得像猫,被抛出后能平稳着地,有意思的美国西点军校机器人!...
  6. java future用法_Java中的多线程知识点
  7. 计算机相关的考试题目,计算机考试相关题目汇总.doc
  8. 无头结点单链表的逆置_单链表的逆置(不带头结点)
  9. drupal7 php版本,让Drupal7识别您的模块
  10. 简单分析MySQL 一则慢日志监控误报问题