目录

秒杀系统

秒杀过程:

用户登录—>点击秒杀—->成功,则减库存,下订单—->失败,则返回

但因为秒杀的时候,系统瞬间访问量很大,对数据库和服务器都有很大的访问压力

优化思路

页面优化

  1. 页面缓存+URL缓存+对象缓存
  2. 页面静态化,前后端分离
  3. 静态资源优化
  4. CDN优化

接口优化

  1. Redis预减库存减少数据库访问
  2. 内存标记减少 Redis访问
  3. RabbitMQ队列缓冲,异步下单,增强用户体验
  4. 访问 Nginx水平扩展

安全优化

  1. 秒杀地址隐藏
  2. 数学公式验证码
  3. 接口防刷

思路实现

对象缓存

可以在第一次访问对象,比如user的时候就放入Redis以后再访问接口的时候,直接从Redis中取出- 即可

页面缓存

利用SpringWebContentThymeleafViewResolver 进行手动渲染,同样的放入Redis缓存中

SpringWebContext ctx = new SpringWebContext(request,response,
    			request.getServletContext(),request.getLocale(), model.asMap(), applicationContext );
    	//手动渲染
    	html = thymeleafViewResolver.getTemplateEngine().process("goods_list", ctx);
    	if(!StringUtils.isEmpty(html)) {
    		redisService.set(GoodsKey.getGoodsList, "", html);
    	}

页面静态化

利用ajax,调用后端接口,前后端交互通过JSON进行,前端页面则抽出页面

spring.resources.add-mappings=true
spring.resources.cache-period= 3600
spring.resources.chain.cache=true 
spring.resources.chain.enabled=true
spring.resources.chain.gzipped=true
spring.resources.chain.html-application-cache=true
spring.resources.static-locations=classpath:/static/

静态资源优化

  1. JS/CSS 压缩,减少流量
  2. 多个JS/CSS组合,减少连接数
  3. CDN就近访问

内存标记减少Redis的访问

// 通过实现InitializingBean,一开始启动的时候就把商品信息放入Redis中,当进行减库存的时候,如果库存小于0,则把对应goodsId的值改为true
public void afterPropertiesSet() throws Exception {
		List<GoodsVo> goodsList = goodsService.listGoodsVo();
		if(goodsList == null) {
			return;
		}
		for(GoodsVo goods : goodsList) {
			redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());
			localOverMap.put(goods.getId(), false);
		}
	}

RabbitMQ进行异步下单

// 整个流程就变为了
1.  预先将要秒杀的商品的库存加入redis中,GoodKey. (但每次访问数据库还可以再进行一次优化,将物品设置个状态位,是否可用)
2. 访问的时候,如果查询到redis库存的信息小于0,则直接返回错误
3. 判断是否已经秒杀到了,如果秒杀到了,则说明之前抢到了,不能进行重复提交
4. 没有秒杀到 构造message,放入MQ队列中,
MQ的处理逻辑
1. 创建消息的发送者,和接收者
发送者逻辑:
1. 对用户和秒杀物品进行发送,顺便打个Log
接收者逻辑:
  	1. 得到物品id,用户,判断是否在redis中存在商品,数据库中库存是否足够,以及是否秒杀到了
 2. 减库存数据库是否成功,成功则创建一个order订单,否则让redis中的goodsid的状态位设置为禁用

// 客户端进行轮询,判断是否秒杀成功
也就是数据库中是否有用户的订单,3个状态:排队中,成功,失败
没有则再判断Redis中的内存标记

超卖问题

  1. 数据库加唯一索引:防止用户重复购买
  2. SQL加库存数量判断:防止库存变成负数

秒杀地址隐藏

思路:

  1. 秒杀开始之前,先去请求接口获取秒杀地址

    1. 接口改造,带上 PathVariable参数
    2. 添加生成地址的接口
    3. 秒杀收到请求,先验证 PathVariable

      涉及path请求路径,先得到接口的请求路径
      首先判断用户是否登录了(useragrument参数检验)
      判断验证码是否正确
      创建秒杀的地址
      UUID+salt的方式取创建,并存入redis中
      返回地址
      
      再去请求真正的地址 /{path}/do_miaosha
      同样是判断用户是否登录了
      然后验证path(去redis中进行验证)
      内存标记,减少redis1访问
      预减库存(incr) < 0 则报错
      ..
      

数学公式验证码

接口限流