⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 网络 「网络」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

今天我们从 7 个不同的维度,讲讲秒杀系统的架构设计,主要知识点如下:

  • Nginx + 前后端分离 + CDN 缓存 + 网关(限流+熔断)
  • 集群的路由层 + Redis(缓存热点数据、分布式锁)
  • MQ 集群
  • 业务处理层
  • 数据库层(读写分离、热点隔离)

1. 秒杀业务的特点

  • 瞬间大量的刷新页面的操作
  • 瞬间大量的抢宝的操作
  • 可能有秒杀器的恶性竞争

2. 总体思路

2.1 削峰限流

  • 前端+Redis拦截,只有redis扣减成功的请求才能进入到下游
  • MQ堆积订单,保护订单处理层的负载,Consumer根据自己的消费能力来取Task,实际上下游的压力就可控了。重点做好路由层和MQ的安全
  • 引入答题验证码、请求的随机休眠等措施,削峰填谷

安全保护

  • 页面和前端要做判断,防止活动未开始就抢单,防止重复点击按钮连续抢单
  • 防止秒杀器恶意抢单,IP限流、UserId限流限购、引入答题干扰答题器,并且对答题器答题时间做常理推断
  • IP黑名单、UserId黑名单功能
  • 过载丢弃:QPS或者CPU等核心指标超过一定限额时,丢弃请求,避免服务器挂掉,保证大部分用户可用

页面优化,动静分离

  • 秒杀商品的网页内容尽可能做的简单:图片小、js css 体积小数量少,内容尽可能的做到动静分离
  • 秒杀的抢宝过程中做成异步刷新抢宝,而不需要用户刷新页面来抢,降低服务器交互的压力
  • 可以使用Nginx的动静分离,不通过传统web浏览器获取静态资源
  • nginx开启gzip压缩,压缩静态资源,减少传输带宽,提升传输速度
  • 或者使用Varnish,把静态资源缓存到内存当中,避免静态资源的获取给服务器造成的压力

异步处理

  • redis抢单成功后,把后续的业务丢到线程池中异步的处理,提高抢单的响应速度
  • 线程池处理时,把任务丢到MQ中,异步的等待各个子系统处理(订单系统、库存系统、支付系统、优惠券系统) 异步操作有事务问题,本地事务和分布式事务,但是为了提升并发度,最好牺牲一致性。通过定时扫描统计日志,来发现有问题的订单,并且及时处理

热点分离

尽量的避免秒杀功能给正常功能带来的影响,比如秒杀把服务器某个功能拖垮了。

分离可以提升系统的容灾性,但是完全的隔离的改造成本太高了,尽量借助中间件的配置,来实现冷热分离。

  • 集群节点的分离:nginx配置让秒杀业务走的集群节点和普通业务走的集群不一样。

  • MQ的分离:避免秒杀业务把消息队列堆满了,普通业务的交易延迟也特别厉害。

  • 数据库的分离:根据实际的秒杀的QPS来选择,热点数据分库以后,增加了分布式事务的问题,以及查询的时候跨库查询性能要差一些(ShardingJDBC有这种功能),所以要权衡以后再决定是否需要分库

  • 避免单点:各个环节都要尽力避免

  • 降级:临时关闭一些没那么重要的功能,比如秒杀商品的转赠功能、红包的提现功能,待秒杀峰值过了,设置开关,再动态开放这些次要的功能

2.2 Nginx的设计细节

  • 动静分离,不走tomcat获取静态资源

server {
listen 8088;
location ~ \.(gif|jpg|jpeg|png|bmp|swf)$ {
root C:/Users/502764158/Desktop/test;
}

location ~ \.(jsp|do)$ {
proxy_pass http://localhost:8082;
}
}
}

  • gzip压缩,减少静态文件传输的体积,节省带宽,提高渲染速度

gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 3;
gzip_disable "MSIE [1-6]\.";
gzip_types text/plain application/x-javascript text/css application/xml text/javascript image/jpeg image/gif image/png;

配置集群负载和容灾,设置失效重连的时间,失效后,定期不会再重试挂掉的节点,参数:

  • fail_timeout默认为10s
  • max_fails默认为1。就是说,只要某个server失效一次,则在接下来的10s内,就不会分发请求到该server上
  • proxy_connect_timeout 后端服务器连接的超时时间_发起握手等候响应超时时间

   upstream  netitcast.com {  #服务器集群名字   
server 127.0.0.1:8080;
server 127.0.0.1:38083;
server 127.0.0.1:8083;
}

server {
listen 88;
server_name localhost;
location / {
proxy_pass http://netitcast.com;
proxy_connect_timeout 1;
fail_timeout 5;
}
}

  1. 集成Varnish做静态资源的缓存
  2. 集成tengine做过载的保护

2.3 页面优化细节

降低交互的压力

  • 尽量把js、css文件放在少数几个里面,减少浏览器和后端交互获取静态资源的次数
  • 尽量避免在秒杀商品页面使用大的图片,或者使用过多的图片

安全控制

  • 时间有效性验证:未到秒杀时间不能进行抢单,并且同时程序后端也要做时间有效性验证,因为网页的时间和各自的系统时间决定,而且秒杀器可以通过绕开校验直接调用抢单
  • 异步抢单:通过点击按钮刷新抢宝,而不是刷新页面的方式抢宝(答题验证码等等也是ajax交互)
  • redis做IP限流
  • redis做UserId限流

2.4 Redis集群的应用

  1. 分布式锁(悲观锁)
  2. 缓存热点数据(库存):如果QPS太高的话,另一种方案是通过localcache,分布式状态一致性通过数据库来控制

分布式悲观锁(参考redis悲观锁的代码)

  • 悲观锁(因为肯定争抢严重)
  • Expire时间(抢到锁后,立刻设置过期时间,防止某个线程的异常停摆,导致整个业务的停摆)
  • 定时循环和快速反馈(for缓存有超时设置,每次超时后,重新读取一次库存,还有货再进行第二轮的for循环争夺,实现快速反馈,避免没有货了还在持续抢锁)

异步处理订单

  • redis抢锁成功后,记录抢到锁的用户信息后,就可以直接释放锁,并反馈用户,通过异步的方式来处理订单,提升秒杀的效率,降低无意义的线程等待
  • 为了避免异步的数据不同步,需要抢到锁的时候,在redis里面缓存用户信息列表,缓存结束后,触发抢单成功用户信息持久化,并且定时的比对一致性

2.5 消息队列限流

消息队列削峰限流(RocketMQ自带的Consumer自带线程池和限流措施),集群。一般都是微服务,订单中心、库存中心、积分中心、用户的商品中心

2.6 数据库设计

  • 拆分事务提高并发度
  • 根据业务需求考虑分库:读写分离、热点隔离拆分,但是会引入分布式事务问题,以及跨库操作的难度

要执行的操作:扣减库存、生成新订单、生成待支付订单、扣减优惠券、积分变动

库存表是数据库并发的瓶颈所在,需要在事务控制上做权衡:可以把扣减库存设置成一个独立的事务,其它操作成一个大的事务(订单、优惠券、积分操作),提高并发度,但是要做好额外的check

update 库存表 set 库存=库存-1 where id=** and 库存>1

2.7 答题验证码的设计

  • 可以防止秒杀器的干扰,让更多用户有机会抢到
  • 延缓请求,每个人的反应时间不同,把瞬间流量分散开来了

验证码的设计可以分为2种:

  • 验证失败重新刷新答题(12306):服务器交互量大,每错一次交互一次,但是可以大大降低秒杀器答题的可能性,因为没有试错这个功能,答题一直在变
  • 验证失败提示失败,但是不刷新答题的算法:要么答题成功,进入下单界面,要么提示打错,继续答题(不刷新答题,无须交互,用js验证结果)。 这种方案,可以在加载题目的时候一起加载MD5加密的答案,然后后台再校验一遍,实现类似的防止作弊的效果。好处是不需要额外的服务器交互。 MD加密答案的算法里面要引入 userId PK这些因素进来来确保每次答案都不一样而且没有规律,避免秒杀器统计结果集

答题的验证:除了验证答案的正确性意外,还要统计反应时间,例如12306的难题,正常人类的答题速度最快是1.5s,那么,小于1s的验证可以判定为机器验证

3. 注意事项

为了提升并发,需要在事务上做妥协:

  • 单机上拆分事务:比如扣减库存表+(生成待支付订单+优惠券扣减+积分变动)是一个大的事务,为了提高并发,可以拆分为2个事务
  • 分库以后引入分布式事务问题,为了保证用户体验,最好还是通过日志分析来人工维护,否则阻塞太严重,并发差。
文章目录
  1. 1. 1. 秒杀业务的特点
  2. 2. 2. 总体思路
    1. 2.1. 2.1 削峰限流
    2. 2.2. 2.2 Nginx的设计细节
    3. 2.3. 2.3 页面优化细节
    4. 2.4. 2.4 Redis集群的应用
    5. 2.5. 2.5 消息队列限流
    6. 2.6. 2.6 数据库设计
    7. 2.7. 2.7 答题验证码的设计
  3. 3. 3. 注意事项