秒杀场景分析和功能设计
脚本回顾
1.先登录到镜像仓库
2.从镜像仓库拉取镜像
3,判断主机中该镜像对象的容器是否启动
4.如果存在,先stop,再删除容器
5.如果容器没启动,直接run
架构图
-
nginx–反向代理、负载均衡
-
网关0zuul
-
微服务项目。。。order、web、user、product、kill、account
-
注册中心eurka
-
每个微服务模块,单独拎出来,都会有自己的数据库,用异步方式做了主从数据库
-
中间件,mq用在高并发场景,kafaka做日志收集,redis、zookeeper做分布式锁
-
redis、mongodb做缓存
-
zipkin、es、es head、kafaka做日志收集
秒杀需求分析
- 促销,吸引用户关注
- 秒杀属于读多写少的场景
- 秒杀流程一定要短
场景分析
- 价格低廉
- 大幅推广,量大
- 瞬时售空
- 定时上架
- 瞬时并发高
技术挑战
- 1.会对网站造成较大的冲击,如果和原有应用部署在一起,必然影响普通的业务,
- 解决方案:将秒杀系统独立部署,甚至使用独立域名,使其与网站完全隔离。即物理隔离
- 2.高并发下的应用、数据库负载在秒杀开始前,会不停狂刷界面以保证不过错过请求,会对应用服务器和数据库造成较大的负载压力。
- 解决方案:重新设计一套单独的页面
- 3.突然增加的网络和带宽,假设页面200k,那么需要的带宽是2g(200k x 10000),超过平时的带宽
- 解决方案:重新购买或租借、将秒杀页面缓存在CDN,同样需要和CDN服务商临时租借新增的出口带宽
- CDN:静态资源服务器加网络转发服务器
- 1.加快界面响应速度,缓存静态页面
- 2.各个地方的站点服务器(湖南、上海、北京、东京),有跨地域访问,此时访问速度是比较慢的,而CDN域名服务器会根据用户所在地解析到就近的CDN服务器,
- 3.域名解析后会指向一个cdn服务器
- 4.主要是为了静态资源加速,可以理解为一种就近缓存
秒杀的架构原则
-
1.尽量将请求拦截在系统上游,传统项目之所以挂,请求都压到了后端数据层,读写锁冲突严重,并发响应慢,几乎所有的请求都在超时,流量虽大,但下单成功的流量很小,一趟火车有2000张票,200w个人来买,基本没有人能买成功,请求效率为0
- 单体项目根本承受不了这么大的流量,网络连接数被耗光了
- 数据库连接池也被打满了,同时上下文切换也很频繁,操作变慢,从而形成恶性循环
-
2.读多写少的场景多使用缓存,这是一个读多写少的应用场景,上面那个案例写比例只有0.001,读占0.999。
秒杀架构设计
- 1.为秒杀而生,更关心的是如何能快速刷新商品页面,在秒杀开始的时候抢先进入下单页面,尽量简化流程和每一步的操作,比如默认只能秒1个
- 2.下单表单也尽可能简单,
前端设计
- 1.静态界面加载js、css,
- cdn节点
- 2.点击秒杀后,按钮置灰
- 3.js限制x秒内只能提交1次
拦截流量(大量的无效刷新)
秒杀站点设计
- 1.nginx层针对同一iP的访问限流
- 2.同一查询在站点层限制访问频率
这样又可以拦截99%流量在站点层(无效流量,恶意请求)
-
高端程序员玩家手里掌握了10w台肉鸡服务器,在常用端口22、8080通过木马植入,来操控,这种秒杀跟普通用户基本没有区别了
-
1.做验证码校验,验证是否是活人
-
2.什么时候弹验证码,做同样的操作过多的时候
-
3.可以提高秒杀的业务门槛,只允许星级用户可以参与秒杀,通过业务员手段限制
秒杀服务处设计
尽量不要把大量的请求瞬时落到数据库,会占满数据库的连接数,其他的用户只能傻傻的等待,就会超时,就会拖慢整个系统
- 1.读请求,cache来扛,memcached,redis单机扛10w+应该没问题
- 2.写请求,用队列,有并行转串行,
秒杀数据库设计
- 主从架构,从的机器不要太多,cache已经起到了对数据库的一种弥补
- 双主与级联复制结合架构
- 真正的写只有一台
- 主要去参照分库分表讲的数据架构
高并发要吞度量or系统保护
qps的计算
秒杀接口的设计指标为:5w/s的qps
例如秒杀系统有20台web服务器,maxclients为500,处理业务的平均响应时间为100ms
那么,web的理论峰值qps为
20*50 /0.1 = 100000 = 10w/s qps
这是绝对理论
maxclients的配置指标?
maxclients是web服务器配置的连接数,是一个线程数,但是并不是线程越多越好,为什么呢?
因为线程多了会增加cpu、内存的计算压力,线程切换时比较耗时的,线程的切换势必会增加用户的响应时间。所以web服务器的连接数要根据cpu、内存等硬件的实际情况来设置。
线程数量与吞度量
- 不是线程数越高,你的单次请求速度和吞吐量就越高
- 压测找到最合适的
- 经验设计值,数据库连接数跟cpu的个数相关,一般是两倍再乘以一个因子
举例
- 5w/s的并发怼过来,实例响应时间是250ms
- 计算出qps = 20*500/0,25 = 40w/s
- 还剩1w没办法处理,会出现请求拒绝的场景
过载保护机制
超过5w直接拒绝
系统安全的矛和盾
- 同一账号发送多次请求
- redis
- 多个账号发送多次请求
- 限制ip
- 多个账号,不同ip
- 业务限制,星级用户,普通用户不能参加
表
- 表名:tp_kill_goods_price 类名: KillGoodsContoller
detail - 查秒杀商品的详细信息
1.先从缓存里面查询数据
String killGoodsDetail = KillConstants.KILLGOOD_DATAIL + ID
KillGoodsSpecPriceDetailVo killGoodsSpecPriceDetailVo = redisTemplate.opsForValue).get()
2.去数据库里面查询数据
这是最常规的写法,可能发生缓存雪崩的问题,不同的接口涉及到的key同时失效—不同的key设置不一样的失效时间。
缓存雪崩----大量请求过来,直接怼数据库了
大量请求,请求不同的接口,不同的接口
- 1.瞬时并发
- 2.瞬时并发,接口涉及到的key,突然失效
解决方案
解决1:在下面查询数据库的方法里面加一个同步块
synchronized(iKillSpecManageService) {
1.再查一次缓存(因为同步块的作用,t1的线程已经缓存了,t2可以直接走缓存了)
2.去数据库里面查询数据
}
解决方法1的补充,做缓存预热,使程序更健壮:
缓存里面一般这种比较热的数据,秒杀商品这种,缓存里面一定要有,启动项目的时候,就必须要把热门数据做缓存预热,