秒杀架构设计的 7 个锦囊! 今天我们从 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默认为10smax_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; } }集成Varnish做静态资源的缓存集成tengine做过载的保护2.3 页面优化细节降低交互的压力尽量把js、css文件放在少数几个里面减少浏览器和后端交互获取静态资源的次数尽量避免在秒杀商品页面使用大的图片或者使用过多的图片安全控制时间有效性验证未到秒杀时间不能进行抢单并且同时程序后端也要做时间有效性验证因为网页的时间和各自的系统时间决定而且秒杀器可以通过绕开校验直接调用抢单异步抢单通过点击按钮刷新抢宝而不是刷新页面的方式抢宝答题验证码等等也是ajax交互另外搜索公众号Linux就该这样学后台回复“猴子”获取一份惊喜礼包。redis做IP限流redis做UserId限流2.4 Redis集群的应用分布式锁悲观锁缓存热点数据库存如果QPS太高的话另一种方案是通过localcache分布式状态一致性通过数据库来控制分布式悲观锁参考redis悲观锁的代码悲观锁因为肯定争抢严重Expire时间抢到锁后立刻设置过期时间防止某个线程的异常停摆导致整个业务的停摆定时循环和快速反馈for缓存有超时设置每次超时后重新读取一次库存还有货再进行第二轮的for循环争夺实现快速反馈避免没有货了还在持续抢锁异步处理订单redis抢锁成功后记录抢到锁的用户信息后就可以直接释放锁并反馈用户通过异步的方式来处理订单提升秒杀的效率降低无意义的线程等待为了避免异步的数据不同步需要抢到锁的时候在redis里面缓存用户信息列表缓存结束后触发抢单成功用户信息持久化并且定时的比对一致性2.5 消息队列限流消息队列削峰限流(RocketMQ自带的Consumer自带线程池和限流措施)集群。一般都是微服务订单中心、库存中心、积分中心、用户的商品中心2.6 数据库设计拆分事务提高并发度根据业务需求考虑分库读写分离、热点隔离拆分但是会引入分布式事务问题以及跨库操作的难度要执行的操作扣减库存、生成新订单、生成待支付订单、扣减优惠券、积分变动库存表是数据库并发的瓶颈所在需要在事务控制上做权衡可以把扣减库存设置成一个独立的事务其它操作成一个大的事务订单、优惠券、积分操作提高并发度但是要做好额外的checkupdate 库存表 set 库存库存-1 where id** and 库存12.7 答题验证码的设计可以防止秒杀器的干扰让更多用户有机会抢到延缓请求每个人的反应时间不同把瞬间流量分散开来了验证码的设计可以分为2种验证失败重新刷新答题12306服务器交互量大每错一次交互一次但是可以大大降低秒杀器答题的可能性因为没有试错这个功能答题一直在变验证失败提示失败但是不刷新答题的算法要么答题成功进入下单界面要么提示打错继续答题不刷新答题无须交互用js验证结果)。这种方案可以在加载题目的时候一起加载MD5加密的答案然后后台再校验一遍实现类似的防止作弊的效果。好处是不需要额外的服务器交互。MD加密答案的算法里面要引入 userId PK这些因素进来来确保每次答案都不一样而且没有规律避免秒杀器统计结果集答题的验证除了验证答案的正确性意外还要统计反应时间例如12306的难题正常人类的答题速度最快是1.5s那么小于1s的验证可以判定为机器验证3. 注意事项为了提升并发需要在事务上做妥协单机上拆分事务比如扣减库存表(生成待支付订单优惠券扣减积分变动)是一个大的事务为了提高并发可以拆分为2个事务分库以后引入分布式事务问题,为了保证用户体验最好还是通过日志分析来人工维护否则阻塞太严重并发差。