【商城秒杀项目】-- 项目总结

转自:https://blog.csdn.net/weixin_42687829/article/details/104535027?utm_source=app

原作者项目代码已上传至gitHub:https://github.com/java-LJ/miaosha.git

最近对商城秒杀项目所用到的技术进行了剖析,技术点还是挺多的,本篇博客就来对商城秒杀项目进行一个总结与整理

项目相关博客汇总

1、【商城秒杀项目】-- 概况

2、【商城秒杀项目】-- 对返回 json 结果的封装、通用缓存 Key 的设计与封装

3、【商城秒杀项目】-- 登录时使用两次 MD5 加密

4、【商城秒杀项目】-- 使用 JSR303 进行参数校验、全局异常处理

5、【商城秒杀项目】-- 分布式 Session 的实现、使用 Aop 校验 Token 令牌

6、【商城秒杀项目】-- 流量削峰应该怎么做

7、【商城秒杀项目】-- 页面缓存、URL 缓存、对象缓存

8、【商城秒杀项目】-- 页面伪静态化

9、【商城秒杀项目】-- 秒杀的业务逻辑、接口的优化

10、【商城秒杀项目】-- 使用 rabbitmq 异步下单

11、【商城秒杀项目】-- 秒杀接口地址隐藏

12、【商城秒杀项目】-- 使用数学图形验证码来进行限流

13、【商城秒杀项目】-- 接口限流防刷

14、关于 springboot 项目使用 @Transactional 注解事务不回滚的问题

项目的亮点

  1. 使用分布式 Seesion,实现让多台服务器同时可以响应
  2. 使用 redis 做缓存提高访问速度和并发量,减少数据库压力,利用内存标记减少 redis 的访问
  3. 使用页面静态化,加快用户访问速度,提高 QPS(每秒查询率),缓存页面至浏览器,前后端分离降低服务器的压力
  4. 使用消息队列完成异步下单,提升用户体验,削峰和限流
  5. 安全性优化:双重 md5 密码校验,秒杀接口地址的隐藏,接口限流防刷,数学公式验证码

主要知识点:

  • 分布式 Seesion

通常秒杀服务实际的应用可能不止部署在一个服务器上,而是分布式的部署在多台服务器,这时候假如用户登录是在第一个服务器,第一个请求到了第一台服务器,这时是没问题的,但是第二个请求到了第二个服务器,那么用户的 session 信息就丢失了,于是需要使用分布式 session 来进行处理

解决:session 同步,无论访问哪一台服务器,session 都可以取得到,利用 redis 缓存的方法,另外使用一个 redis 服务器专门用于存放用户的 session 信息,这样就不会出现用户 session 丢失的情况(每次需要 session,从缓存中取即可)

  • 使用 redis 缓解数据库压力

本项目大量的使用了缓存技术,包括用户信息缓存(分布式 session),商品信息的缓存,商品库存缓存,订单的缓存,页面缓存,对象缓存等来减少了对数据库服务器的访问

  • 通用缓存 key 封装

大量的缓存引用也出现了一个问题,如何识别不同模块中的缓存(即 key 值重复,如何辨别是不同模块的 key)

解决:利用一个抽象类,定义 BaseKey(前缀),在里面定义缓存 key 的前缀以及缓存的过期时间从而实现将缓存的 key 进行封装;让不同模块继承它,这样每次存入一个模块的缓存的时候,就加上这个缓存特定的前缀,以及统一制定不同的过期时间

  • 页面静态化(前后端分离)

页面静态化的主要目的是为了加快页面的加载速度,将商品的详情和订单详情页面做成静态 HTML(纯的 HTML),数据的加载只需要通过 ajax 来请求服务器,并且做了静态化 HTML 页面可以缓存在客户端的浏览器

  • 消息队列完成异步下单

使用消息队列完成异步下单,可以提升用户体验、削峰和限流

使用思路:

  1. 系统初始化的时候,把商品库存数量加载到 Redis 里面去
  2. 后端收到秒杀请求,先 Redis 预减库存,如果库存已经到达临界值的时候,就不需要继续请求下去,直接返回秒杀失败,即后面的大量请求无需给系统带来压力
  3. 判断这个秒杀订单形成没有,判断是否已经秒杀到了,避免一个账户秒杀多个商品,判断是否重复秒杀
  4. 库存充足,且无重复秒杀,将秒杀请求封装后放入消息队列,同时给前端返回一个 code (0),即代表返回排队中(返回的并不是失败或者成功,此时还不能判断)
  5. 前端接收到数据后,显示排队中,并根据商品 id 轮询请求服务器查询秒杀的结果(考虑 200ms 轮询一次)
  6. 后端 RabbitMQ 监听秒杀 MIAOSHA_QUEUE 这个名字的通道,如果有消息过来,就获取到传入的信息,执行真正的秒杀之前,要判断数据库的库存,判断是否重复秒杀,然后执行秒杀事务(秒杀事务是一个原子操作:库存减 1,下订单,写入秒杀订单)
  7. 此时,前端根据商品 id 轮询请求接口 result,查看是否生成了商品订单,如果请求返回 - 1 就代表秒杀失败,返回 0 就代表排队中,返回 > 0 就代表秒杀成功
  • 安全性优化

双重 md5 密码校验、秒杀接口地址的隐藏、接口限流防刷、数学公式验证码

  • 优雅的代码编写

对接口的输出结果做了一个 Result 封装、对错误的代码做了一个 CodeMsg 封装、对访问缓存做了一个 key 的封装

项目难点及问题解决

1、使用了大量缓存,那么就存在缓存击穿和缓存雪崩以及缓存一致性等问题

  • 缓存穿透指的是对某个一定不存在的数据进行请求,该请求将会穿透缓存到达数据库

解决方案:对这些不存在的数据缓存一个空数据,对这类请求进行过滤

  • 缓存雪崩指的是由于数据没有被加载到缓存中,或者缓存数据在同一时间大面积失效(过期),又或者缓存服务器宕机,导致大量的请求都到达数据库

解决方案:为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现;为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用;也可以进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩

例如:首先针对不同的缓存设置不同的过期时间,比如 session 缓存,在 userKey 这个前缀中,设置是 30 分钟过期,并且每次用户响应的时候更新缓存时间,这样每次取 session,都会延长 30 分钟,相对来说,就减少了缓存过期的几率

  • 缓存一致性要求数据更新的同时缓存数据也能够实时更新

解决方案:在数据更新的同时立即去更新缓存,首先尝试从缓存读取,读到数据则直接返回;如果读不到,就读数据库,并将数据会写到缓存,并返回;在读缓存之前先判断缓存是否是最新的,如果不是最新的先进行更新,需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)

2、大量的使用缓存,对于缓存服务器也有很大的压力,如何减少 redis 的访问

在 redis 预减库存的时候,内存中维护一个 localOverMap 作为内存标记,当没有库存的时候,将其设置为 true;每次秒杀业务访问 redis 之前,先查一下 map 标记,如果为 true 说明没有库存,就直接返回秒杀失败,无需再去请求 redis 服务器

3、在高并发请求的业务场景,大量请求来不及处理,甚至出现请求堆积的情况

使用消息队列来异步处理请求,每次请求过来先不去处理请求,而是放入消息队列,然后在后台设置一个监听器,分别监听不同业务的消息队列,有消息来的时候才进行秒杀业务逻辑,这样防止多个请求同时操作的时候,出现数据库连接过多的异常

4、怎么保证一个用户不能重复下单

在秒杀订单表中建立一个唯一索引(由用户 Id 与商品 Id 组成),使得同一条记录只能入库一次,防止一个用户同时发出多个请求秒杀到多个商品

唯一索引是在数据库表结构中对字段添加唯一索引后数据库进行存储操作时数据库会判断库中是否已经存在此数据,不存在此数据时才能进行插入操作。这虽然是个小技能,但实际上在业务开发中是个很实用的技能,比如在高并发业务中,数据库如何杜绝数据并发插入两条相同的订单号?添加一个唯一索引是最快捷的方法之一,当然是添加索引还是通过业务代码去解决因业务而定

5、怎么解决超卖现象

超卖场景:不同用户在读请求的时候,发现商品库存足够,然后同时发起请求,进行秒杀操作减库存,导致库存减为负数

最简单的方法就是在更新数据库减库存的时候,进行库存限制条件,在 reduceStock (GoodsVo goodsvo) 这个方法里,sql 要多加一个 stock_count>0 的条件,使用数据库特性来保证超卖的问题,只有 stock_count 还大于 0 的时候才去 update 将 stock_count 进行减 1 的操作

6、页面静态化的过程及什么是浏览器缓存

将 HTML 静态页面缓存在客户端浏览器,只有数据通过 ajax 异步调用接口来获取,仅仅交互的是部分数据,可以减少带宽,也加快用户访问的速度

浏览器缓存就是把一个已经请求过的 Web 资源(如 HTML 页面、图片、js、数据等)拷贝一份副本储存在浏览器中,缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,如果是相同的 URL,缓存会根据缓存机制决定是直接使用副本来响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个 URL 地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页,只有当网站明确标识资源已经更新的时候,浏览器才会再次下载网页

7、秒杀架构设计理念

  • 限流: 鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端
  • 削峰:对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值,而高峰值流量是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术
  • 异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式
  • 内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘 IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升
  • 可拓展:当然如果我们想支持更多用户,更大的并发,最好就是将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了

8、秒杀系统架构设计思路

  • 将请求拦截在系统上游,降低下游压力:秒杀系统特点是并发量极大,但实际秒杀成功的请求数量却很少,所以如果不在前端拦截很可能造成数据库读写锁冲突,最终请求超时
  • 使用缓存:使用缓存可极大提高系统读写速度
  • 使用消息队列:消息队列可以削峰、拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理

9、假如减了库存但用户没有支付,怎么将库存还原继续进行抢购

设定一个最长付款时间,比如 30 分钟,后台有个定时任务(使用定时器 Timer),轮询超过 30 分钟的待付款订单(从数据库里面判定订单状态),然后关闭订单,恢复库存

  • 4
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值