项目简介
本项目主要是模拟应对大并发场景下,如何完成商品的秒杀,以及针对秒杀场景下为应对大并发所做的优化。
文章目录
项目地址:https://github.com/noodou/seckill
项目的技术结构如下图所示:
数据库设计如下图:
这里的数据库设计只是为了模拟秒杀场景,实际的数据库会复杂很多。需要注意的是,因为参与秒杀的只有部分商品,所以单独建立一个miaosha_goods
存储于秒杀商品相关的字段。
- 秒杀用户表:
miaosha_user
- 商品表:
goods
- 参与秒杀的商品表:
miaosha_goods
- 秒杀订单表:
miaosha_order
- 订单表:
order_info
秒杀场景下主要解决的问题:
- 分布式会话
- 用户登录、商品列表、商品详情、订单详情模块
- 缓存优化
- 系统压测,测试系统的QPS
- 信息队列
- 接口安全
登录实现
登录部分主要有以下几个部分:
-
明文密码两次MD5处理
-
JSR303参数检验和全局异常处理器
-
分布式Session
明文密码两次MD5处理
- 客户端:C_PASS=MD5(明文+固定salt)
- 服务端:S_PASS=MD5(C_PASS+随机salt)
加密:出于安全考虑
第一次 (在前端加密,客户端):密码加密是(明文密码+固定盐值)生成md5用于传输,目的,由于http是明文传输,当输入密码若直接发送服务端验证,此时被截取将直接获取到明文密码,获取用户信息。
加盐值是为了混淆密码,原则就是明文密码不能在网络上传输。
第二次:在服务端再次加密,当获取到前端发送来的密码后。通过MD5(密码+随机盐值)再次生成密码后存入数据库。
防止数据库被盗的情况下,通过md5反查,查获用户密码。方法是盐值会在用户登陆的时候随机生成,并存在数据库中,这个时候就会获取到。
第二次的目的:
黑客若是同时黑掉数据库,通过解析前端js文件,知道如果md5加密的过程,就知道此时用户的密码。
但是此时我们要是在后端加入随机盐值和传输密码的md5组合,黑客是无法知道通过后端密码加密过程的,从而无法知道密码。
JSR303参数检验和全局异常处理器
JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解。如@NotNull
、@Email
、@Max
等。
在这个系统中,我们自定义了一个注解@IsMobile
完成手机号码的参数检验,@IsMobile
的校验处理器为IsMobileValidator
。
定义一个全局异常GlobalException
和全局异常处理器GlobalExceptionHandler
,可以完成系统异常的捕获和异常的统一处理。
分布式Session
在用户登录成功之后,将用户信息存储在redis中,然后生成一个token返回给客户端,这个token为存储在redis中的用户信息的key,这样,当客户端第二次访问服务端时会携带token,首先到redis中获取查询该token对应的用户使用是否存在,这样也就不用每次到数据库中去查询是不是该用户了,从而减轻数据库的访问压力。
秒杀功能的实现
-
数据库设计
-
商品列表页
-
商品详情页
-
订单详情页
页面优化技术
- 页面级缓存+URL缓存+对象缓存
- 页面静态化,前后端分离
- 静态资源优化
- CDN优化
页面级缓存+URL缓存+对象缓存
所谓页面缓存,指的是对于服务端的请求,不直接从系统中获取页面资源,而是先从缓存中获取页面资源,如果缓存中不存在页面资源,则系统将渲染页面并存储页面到缓存中,然后将页面返回。
来看商品列表页的请求过程;请求到服务端,服务端查询数据库中的商品列表信息然后存储在Model
对象中,Thymeleaf
页面获取在Model
对象中的商品列表信息然后动态渲染,再返回给客户端。如果每次请求都做这样的工作,势必会对服务器和系统造成一定的压力(系统的压力主要来源于每次Thymeleaf
页面获取在Model
对象的信息都要渲染一次),所以可以做一个页面级的缓存,减轻数据库和系统的压力。
在本项目中,我们对商品列表页做一个缓存,因为商品列表页的数据相对表话不是太频繁,所以将其缓存在redis
中,这样不用每次都查询数据库中的商品信息,然后再使用Thymeleaf
渲染返回,而是直接从redis
中返回。另外,由于商品列表页请求返回的是html
,所以这里使用ThymeleafViewResolver
手动渲染页面,这样就可以将页面直接通过系统返回给客户端。(详细过程在edu.uestc.controller.GoodsListController#toList
中)。
而所谓URL缓存,实际上和页面缓存是一样的,在本项目中,我们对商品详情页做了缓存,商品详情页的请求需要goodsId
,也就是说,对每一个goodsId
都做了一个缓存,其他的和商品列表页的缓存思路是一致的,只不过商品取详情页是需要动态的根据goodsId
来取。
通过上面的缓存差异可知,URL缓存和页面缓存的不同之处在于,URL缓存需要根据URL中的参数动态地取缓存,而页面缓存则不需要。
一般来讲,URL缓存和页面缓存的缓存时间都比较短。在本项目中,我们设置商品详情页</