技术点介绍:
项目整体结构:
项目框架搭建:
1、SpringBoot环境搭建
2、集成Thymeleaf,RespBean
3、MyBatis
分布式会话:
1、用户登录
a.设计数据库
b.铭文密码二次MD5加密
c.参数校验+全局异常处理
2、共享Session
a.SpringSession
b.Redis
功能开发:
1、商品列表
2、商品详情
3、秒杀
4、订单详情
系统压测:
1、JMeter
2、自定义变量模拟多用户
3、JMeter命令行的使用
4、正式压测
a.商品列表
b.秒杀
页面优化:
1、页面缓存+URL缓存+对象缓存
2、页面静态化,前后端分离
3、静态资源优化
4、CDN优化
接口优化:
1、Redis预减库存减少数据库的访问
2、内存标记减少Redis的访问
3、RabbitMQ异步下单
a.SpringBoot整合RabbitMQ
b.交换机
安全优化:
1、秒杀接口地址隐藏
2、算术验证码
3、接口防刷
主流的秒杀方案
主要解决两个问题:
(1)并发读
(2)并发写
秒杀业务特点:
1、瞬时并发量大:大量用户都会在同一时间抢购,网站流量激增。
2、库存少:一般都是低价限量,而访问数量远远大于库存数量,只有少数人成功。
3、业务流程简单:流程短,立即购买,下订单,减库存。
4、前期预热:对于还未开启活动的秒杀商品,以倒计时的方式显示,只能访问不能下单。
事务:提交订单,生成订单,支付,扣减库存
业务拆分:订单服务,支付服务,库存服务(服务与服务之间需要引入RabbitMQ,全部业务通过订单ID进行交互)
1、限流(利用redis的计数器):只能让秒杀成功的一小部分人进入到后台,和数据库进行交互,来减少数据库服务器的压力。
2、缓存:将部分业务逻辑写道缓存里,例如:商品限购数量、秒杀政策等。
3、异步(使用RabbitMQ):将业务逻辑拆分,减少服务器压力,例如:正常业务流程是下订单、付款、减少库存同一时间完成,秒杀可以将业务逻辑拆分。
4、预热:商家进行宣传,并提前设置好秒杀的商品、秒杀时间、限购数量,将设置的商品写入redis缓存。
5、展示:页面分为两层,页面分为两层,第一层是商品列表页,第二层是商品详情页,通过商品列表页连接进入商品详情页,秒杀开始前,展示商品秒杀倒计时,不允许操作提交订单,只允许查看商品详情,秒杀开始时,展示商品秒杀到期时间。
6、提交订单:秒杀提交完成订单将redis缓存里的数量减少并提示支付。
7、队列操作:当支付成功后,将秒杀成功详情写入rabbitMQ,订单服务监听接收消息写入订单,库存服务进行监听接收消息减少库存。
8、时间服务器:页面服务端通过负载进行部署,各服务器时间可能会不一致,因此增加时间服务,来提供统一的时间。
Redis的应用:
Redis缓存商品数量、秒杀政策。商家对秒杀政策、商品限量进行设置,设置完成写入Redis。消费者访问商品详情,提交订单之后,从Redis中减少商品数量。
Redis里存取内容:
1、在政策新增的时候存入,key的值为:LIMIT_POLICY_{sku_id},value的值为政策内容
2、商品列表取数据时,通过key(LIMIT_POLICY_{sku_id}),取出政策内容。
3、政策到期之后,自动删除。
RabbitMQ的应用(消费者生产者模式):
消息队列提供一个异步通信机制,消息的发送者不必一直等待到消息被成功处理才返回,而是立即返回。消息中间件负责处理网络通信,如果网络连接不可用,消息被暂存于队列当中,当网络畅通的时候在将消息转发给相应的应用程序或者服务,当然前提是这些服务订阅了该队列。如果在商品服务和订单服务之间使用消息中间件,既可以提高并发量,又降低服务之间的耦合度。
消费者提交订单,自动写入订单队列:
订单队列:订单服务监听订单队列,接收到消息之后将队列信息写入数据库订单表。
消费者付款之后,更新订单状态,更新成功之后写入库存队列
库存队列:库存服务监听库存队列,接收到消息之后将库存信息写入数据库减少库存。
订单和支付异步处理的执行流程:
- 在生成订单的时候,将订单信息存放到orderinformation的消息队列
- 在点击支付订单后,将支付信息存放到消息队列
- 处理订单的消息队列,将消息队列中的数据保存到数据库
- 处理支付订单队列,将支付订单队列的数据更新到数据库
问题:
Q:如何应对瞬时大流量高并发?
高并发的解决思路是分层过滤,分而治之。具体方法是:页面静态化、缓存预热、异步化、削弱值填谷,通过消息队列异步地创建订单。
Q:有限库存,如何防止超卖?
将库存信息加载到Redis中,将MySQL的访问压力转移到Redis上,直接通过Redis来判断并减少库存。使用Redis的原理是:
1、缓存库存信息,大部分数据请求都被Redis挡住了,保护了MySQL。
2、检查Redis库存和扣减Redis库存是两步操作,合并成一个整体,保证原子操作性。
3、即使Redis侧方行可以创建订单了,到MySQL的时候也要再检查一次。
用户——>Lua脚本读Redis库存并扣减
——> 扣减不成功,秒杀结束
——> 扣减成功,锁定数据库库存,创建订单——>付款——> 库存扣减
Q:当秒杀的用户量超过预计,请求量超过服务器最大承载压力怎么办?
流量控制,其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,保护系统不会被压垮,从而保障应用的高可用性。
Q:当有服务出现故障,不可用时如何应对?
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。
Q:如何限制用户购买商品件数?
1、直接查询数据库订单表,查询用户是否已经有购买成功的记录。(但是这种操作可能会带来这些问题:查询量太大,需要全量扫描订单表查询;查询订单表响应时间长;QPS过高,数据库压力太大)
订单表——> 查询订单 ——> 判断是否有订单——>下单成功(未达上限)
——>下单失败(达到上限)
2、一般使用Redis提供的计数功能,记录用户的购买次数
下单请求 —查询购买次数—> Redis ——>判断购买次数限制 ——>下单失败(超过)
——>实际下单、付款等操作(未超过)——>购买成功,记录购买次数
Q:应对恶意请求和爬虫?
采用验证码机制
抢购 ——> 填写验证码 ——> 进入抢购服务
Q:秒杀系统架构设计优化
前端浏览器秒杀页面 —> 中间代理服务 —>后端服务器 —> 数据库层
根据秒杀系统流程优化设计思路:将请求拦截在系统上有,降低下游压力。在一个并发量大,实际需求小的系统种,应当尽量在前端拦截无效流量,降低下游服务器和数据库的压力,不然很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时。
整体设计思路和优化点:
限流:屏蔽掉无用的流量,允许少部分流量流向后端。
削峰:瞬时大流量峰值容易压垮系统,解决这个问题是重中之重。常用的消峰方法有异步处理、缓存和消息中间件等技术。
异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。
内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。
可拓展:当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了。像淘宝、京东等双十一活动时会增加大量机器应对交易高峰。
消息队列:消息队列可以削峰,将拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理。
充分利用缓存:利用缓存可极大提高系统读写速度。
前端方案:
静态资源缓存:将页面上的所有可以静态化的元素全部静态化尽量减少动态元素,通过CDN缓存静态资源,来抗峰值。
用户限流:在某段时间内只允许用户提交一次请求,比如可以采取IP限流
后端方案:
限制同一UserID访问频率:尽量拦截浏览器请求,但针对某些恶意攻击或其它插件,在服务端控制层需要针对同一个访问uid,限制访问频率。
当用户量非常大的时候,拦截流量后的请求访问量还是非常大,此时仍需进一步优化。
业务分离:将秒杀业务系统和其他业务分离,单独放在高配服务器上,可以集中资源对访问请求抗压。
采用消息队列缓存请求:将大流量请求写到消息队列缓存,利用服务器根据自己的处理能力主动到消息缓存队列中抓取任务处理请求,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。
利用缓存应对读请求:对于读多写少业务,大部分请求是查询请求,所以可以读写分离,利用缓存分担数据库压力。
利用缓存应对写请求:缓存也是可以应对写请求的,可把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。
Q:使用Redis中间件缓存动态资源的好处?
提高访问速度,减少对数据库的链接的打开、关闭,
Q:为什么不用JVM内存而使用Redis作为缓存呢?
JVM 内存较小,隔一段时间会自动进行垃圾回收。
JVM和业务程序绑定在一起了,如果程序出错,JVM也会停止,这样就导致缓存数据丢失。
如果使用Redis,除了缓存比较大之外,还实现了缓存数据和业务程序的分离,即使运行程序出现错误,也不会影响缓存。