前言
俗话说的好,冰冻三尺非一日之寒,滴水穿石非一日之功,罗马也不是一天就建成的。两周前秒杀案例初步成型 。同时也收到了不少小伙伴的建议 。我从不认为分布式、集群、秒杀这些就应该是大厂的专利,在互联网的今天无论什么时候都要时刻武装自己,只有这样,也许你的春天就在明天。
在开发秒杀系统案例的过程中,前面主要分享了队列、缓存、锁和分布式锁以及静态化等等。缓存的目的是为了提升系统访问速度和增强系统的处理能力;分布式锁解决了集群下数据的安全一致性问题;静态化无疑是减轻了缓存以及DB层的压力。
限流
然而再牛的机器,再优化的设计,对于特殊场景我们也是要特殊处理的。就拿秒杀来说,可能会有百万级别的用户进行抢购,而商品数量远远小于用户数量。如果这些请求都进入队列或者查询缓存,对于最终结果没有任何意义,徒增后台华丽的数据。对此,为了减少资源浪费,减轻后端压力,我们还需要对秒杀进行限流,只需保障部分用户服务正常即可。
就秒杀接口来说,当访问频率或者并发请求超过其承受范围的时候,这时候我们就要考虑限流来保证接口的可用性,以防止非预期的请求对系统压力过大而引起的系统瘫痪。通常的策略就是拒绝多余的访问,或者让多余的访问排队等待服务。
限流算法
任何限流都不是漫无目的的,也不是一个开关就可以解决的问题,常用的限流算法有:令牌桶,漏桶。
令牌桶
令牌桶算法是网络流量整 形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送(百科)。
令牌桶
在秒杀活动中,用户的请求速率是不固定的,这里我们假定为10r/s,令牌按照5个每秒的速率放入令牌桶,桶中最多存放20个令牌。仔细想想,是不是总有那么一部分请求被丢弃。
漏桶
漏桶算法的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量(百科)。
漏桶
令牌桶是无论你流入速率多大,我都按照既定的速率去处理,如果桶满则拒绝服务。
应用限流
Tomcat
在Tomcat容器中,我们可以通过自定义线程池,配置最大连接数,请求处理队列等参数来达到限流的目的。
threadpool(源自网络)
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默认是0
limit_req zone=api_read burst=5;
#连接数限制,每个IP并发请求为2
limit_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
老罗+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 501
Percentage of the requests served within a certain time (ms)
50% 500
66% 500
75% 500
80% 500
90% 501
95% 501
98% 501
99% 501
100% 501 (longest request)
源码:从0到1构建分布式秒杀系统(https://gitee.com/52itstyle/spring-boot-seckill)
总结
以上限流方案,只是针对此次秒杀案例做一个简单的小结,大家也不要刻意区分那种方案的好坏,只要适合业务场景就是最好的。
参考
-
https://github.com/openresty/lua-resty-limit-traffic
-
https://blog.52itstyle.com/archives/1764/
-
https://blog.52itstyle.com/archives/775/
出处:https://blog.52itstyle.com/archives/2982/
相关推荐
Java分布式秒杀系统源码,从0到1构建分布式秒杀系统,用户规模可大可小,几百或者上千人的活动单体架构足以可以应付,简单的加锁、进程内队列就可以轻松搞定。一旦上升到百万、千万级别的规模可以考虑分布式集群来...
java毕业设计分布式秒杀系统企业级实战(源码)java毕业设计分布式秒杀系统企业级实战(源码)java毕业设计分布式秒杀系统企业级实战(源码)java毕业设计分布式秒杀系统企业级实战(源码)java毕业设计分布式秒杀...
分布式秒杀系统是一种在高并发环境下处理大量用户请求的技术,常用于电商平台的限时抢购活动。这个系统基于一系列先进的IT技术构建,包括JDK 1.7、Maven、Mysql、Eclipse、SpringBoot 1.5.10、Zookeeper 3.4.6、...
本项目是基于Dubbo的分布式秒杀项目。 项目特性: 秒杀的时候系统的并发量会非常的大,系统要在短短的几秒内完成非常大的访问处理。 并发量大的同时,网络的流量也会瞬间变的很大。 秒杀项目特性下,可能会存在...
一旦上升到百万、千万级别的规模就要考虑分布式集群来应对瞬时高并发。 构层级 一般商家在做活动的时候,经常会遇到各种不怀好意的DDOS攻击(利用无辜的吃瓜群众夺取资源),导致真正的我们无法获得服务!所以说高防IP...
SpringBoot开发案例从0到1构建分布式秒杀系统,项目案例基本成型,逐步完善中。 - 分流、分流、分流,重要的事情说三遍,再牛逼的机器也抵挡不住高级别的并发。 - 限流、限流、限流,毕竟秒杀商品有限,防刷的前提下...
综上所述,Spring Boot分布式秒杀系统通过微服务架构、分流、限流、缓存和异步处理等手段,实现了高效、稳定的秒杀服务。在开发过程中,还需要结合实际情况调整参数,监控系统性能,以达到最优状态。文件"spring-...
百度云的王耀,作为主任架构师、IaaS技术负责人以及braft开源项目的负责人,分享了如何使用braft快速构建分布式系统。 分布式系统是独立计算机的集合,对用户来说就像一个单一的相关系统。这种系统的需求包括可伸缩...
spring-boot-seckill分布式秒杀系统是一个用SpringBoot开发的从0到1构建的分布式秒杀系统,项目案例基本成型,逐步完善中。 开发环境: JDK1.8、Maven、Mysql、IntelliJ IDEA、SpringBoot1.5.10、zookeeper3.4.6、...
本源码提供了一个基于Java和Lua的分布式秒杀系统的设计。项目包含273个文件,其中包括92个...此外,还有3个GIF文件。该项目旨在从零开始构建一个分布式秒杀系统,通过Java和Lua技术实现,以提升系统的可扩展性和性能。
spring-boot-seckill分布式秒杀系统是一个用SpringBoot开发的从0到1构建的分布式秒杀系统,项目案例基本成型,逐步完善中。 开发环境: JDK1.8、Maven、Mysql、IntelliJ IDEA、SpringBoot1.5.10、zookeeper3.4.6、...
基于Java的分布式秒杀系统 项目描述 本系统将不同模块解耦成分布式架构,模块之间采用 RabbitMQ 和自己开发的 RPC 框架进行通信。除了实现基本的注册和登录、查看商品列表、秒杀、下单等功能,项目中还针对高并发...
本文将深入探讨如何利用SpringBoot技术栈,从零开始构建一个完整的分布式秒杀系统。 一、项目背景与目标 1. 秒杀系统的目标是处理大量用户在同一时间对有限商品的抢购需求,这对系统的性能和并发处理能力提出了极...
10. 安全性:在秒杀系统中,还需要考虑防止恶意攻击,例如DDoS攻击,可以通过限流、验证码等方式提高系统安全性。 本项目通过整合SpringBoot、Redis和RabbitMQ,构建了一个具备高并发处理能力、高可用性和良好扩展...
基于Spring Boot的分布式秒杀系统 项目概述 本项目是一个基于Spring Boot的分布式秒杀系统,集成了多种技术和中间件,旨在提供高效、可靠的秒杀服务。系统包含多个模块,分别负责订单管理、商品管理、消息队列...
# 基于Spring Boot的分布式秒杀系统 ## 项目简介 本项目是一个基于Spring Boot框架的分布式秒杀系统,旨在提供高效、可靠的秒杀服务。系统通过Spring Boot实现后端逻辑,结合Redis进行缓存管理,确保在高并发场景...
# 基于Spring Boot和Redis的分布式秒杀系统 ## 项目简介 本项目是一个基于Spring Boot框架和Redis缓存的分布式秒杀系统。它旨在处理高并发场景下的秒杀活动,确保系统的稳定性和数据的一致性。项目结合了多种...
从文档中可以了解到,凤凰架构是一套结合作者实践经验的理论总结,旨在帮助技术人员理解和掌握构建现代分布式系统的关键技术点,如微服务、服务网格、无服务架构等。微服务架构通过将单一应用程序划分成一组小服务,...