秒杀商城代码流程详解(乐字节)

响应实体类RespBean

1、登录

  1. 访问/login/toLogin返回前端页面,页面中输入用户名和密码

  2. 点击登录按钮提交表单,其中密码进行了一次md5加密。通过ajax访问"/login/doLogin",

  3. 将前端返回的数据注入登陆类LoginVo,并通过注解进行校验格式正确性。例如@IsMobile

  4. 使用服务层的登录接口

  5. 在实现类中通过调用mapper层的selectById方法在数据库根据手机号获得用户类user。验证用户和密码,其中密码又进行了一次md5加密。都正确后,通过uuid生成cookie并与用户信息一起存入redis(对象缓存,永不失效),将cookie值写入RespBean并返回。

  6. 前端页面根据响应实体类的状态码判断是否成功,若成功,访问"/goods/toList",进入商品列表页。

    • 分布式session问题

      通过nginx实现反向代理,让用户访问代理服务器,nginx分配请求到其他服务器,以应对高并发。由于 Nginx 使用默认负载均衡策略(轮询),请求将会按照时间顺序逐一分发到后端应用上。也就是说刚开始我们在 Tomcat1 登录之后,用户信息放在 Tomcat1 的 Session 里。过了一会,请求又被 Nginx 分发到了 Tomcat2 上,这时 Tomcat2 上 Session 里还没有用户信息,于是又要登录。

      将用户信息统一存储到redis,让服务器都来redis读取。

    • 全局异常处理

      在开发中,不管是dao层、service层还是controller层,都有可能抛出异常,在Springmvc中,能将所有类型的异常处理。从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护。SpringBoot全局异常处理方式主要两种: 使用 @ControllerAdvice 和 @ExceptionHandler 注解。使用 ErrorController类 来实现 。

      本项目主要使用第一种。判断抛出的异常是否为我们定义的异常类,是则返回类中信息。

    • 优化登录

      spring自定义addArgumentResolvers参数解析器解决对用户的健壮性判断,可以简化每个接口都对用户进行校验。校验时从ThreadLocal获取用户。

    • 对象缓存如何解决redis和数据库的缓存一致性问题

      在更新用户信息时,先在数据库中更新信息,成功后删除redis中的缓存。后续再次登录时会将新的用户信息存入redis。·

2、商品列表页和详情页

逆向工程生成所需的所有类

  1. 从redis中获取页面,如果存在,直接返回。

  2. 如果不存在,调用service层,dao层中mybatis-plus实现的findGoodsVo方法,将返回的商品信息添加到model中。手动渲染页面,存入redis,并返回。(服务器端只需要一次拼接模板和渲染页面的操作,后续可以直接读取)失效时间60s。

  3. 点击详情,跳转到商品详情页面。在路径中传入goodsId(前端跳转)

  4. 通过<script>标签,用ajax访问"/goods/detail/" + goodsId,调用dao层方法从数据库中获得商品信息,并封装为详情类返回。

  5. 信息获取成功,将返回对象的信息渲染到页面上。通过返回数据,前端编写计数器完成秒杀倒计时。

    • 页面静态化

      将以前在controller层完成的页面跳转,变为前端页面间的跳转。利用浏览器的缓存,只需加载一次静态资源。在页面中利用ajax访问controller层接口,实现数据的更新。

3、秒杀

seckillController实现了InitializingBean接口,重写afterPropertiesSet方法,将数据库中的秒杀商品和库存数量存入redis。将redis内存标记初始化为false。以便通过预减库存防止超卖。

编写了一个注解@AccessLimit(second=5,maxCount=5,needLogin=true),实现接口防刷功能,采用计数器限流算法。限制接口在5秒内最大访问次数为5次,且需要登录账户。

具体实现是通过实现HandlerInterceptor拦截器接口,重写preHandle方法。HandlerMethod.getMethodAnnotation(AccessLimit.class);获取注解类。从request请求中得到cookie的值,进而在redis里获取user,并存入ThreadLocal。当前用户存入当前线程,避免多线程下用户信息的紊乱。以请求路径和用户id作为key值,在redis中查询。如果存在,则值加一,否则存入redis并设置失效时间。若不满足条件,则构建返回对象,返回错误信息(因为这个方法返回值为布尔类型)。

扩展:其他接口防刷算法

滑动窗口限流算法:本质上也是一种计数器,只是通过以时间为维度的可滑动窗口设计,来减少了临界值带来的并发超过阈值的问题。每次进行数据统计的时候,只需要统计这 个窗口内每个时间刻度的访问量就可以了。

漏桶限流算法:它是一种恒定速率的限流算法,不管请求量是多少,服务端的处理效率是恒定的。

相对漏桶算法来说,它可以处理突发流量的问题。它的核心思想是,令牌桶以恒定速率去生成令牌保存到令牌桶里面,桶的大小是固定的,令牌桶满了以后就不再生成令牌。

令牌桶限流算法:每个客户端请求进来的时候,必须要从令牌桶获得一个令牌才能访问,否则排队等待。在流量低峰的时候,令牌桶会出现堆积,因此当出现瞬时高峰的时候,有足够多的令牌可以获取,因此令牌桶能够允许瞬时流量的处理。

  1. 输入验证码,点击秒杀按钮,访问接口/seckill/path

  2. 验证码通过引入依赖实现。生成的验证码存入redis。可以防止脚本恶意抢购,也可以通过影响用户请求秒杀的时间来缓解服务器压力。

  3. 接口首先校验验证码,验证码正确后调用service层生成随机路径path返回并存入redis。

  4. 前端访问 隐藏接口'/seckill/' + path + '/doSeckill',验证path是否与redis一致。

  5. 查询redis中的秒杀订单表,是否存在以用户和商品id为键值的信息。若存在,则说明该用户购买过该商品,禁止再次买入。

  6. 判断内存中该商品id是否库存已经被清空,若已经为0,则返回错误信息,以减少redis的访问量。

  7. 在redis中预减库存,返回减完以后的库存值。此处使用lua脚本实现原子操作,需要在配置类中注册Bean。具体逻辑为查找输入的键值,若存在,获取它映射的值,判断值是否大于零,如果还有库存,减一并返回新的库存,否则返回-1;

  8. 如果返回值为负值,则标记该商品库存为空。将redis内库存加一(保证一致性,返回的-1,库存最后应该为0)。

  9. 如果返回值非负值,利用rabbitmq发送消息,发送完消息后返回0。

    10-13为异步操作

  10. 具体实现:将用户和商品封装为消息对象,配置rabbitmq(使用topic模式),包括绑定队列、交换机和设置路由。新建发送类,写发送方法(配置交换机和设置路由)。

  11. @RabbitListener(queues = "seckillQueue")接受类监听该队列。在接受类中再次进行健壮性判断(重复购买等),若都符合,进行秒杀操作。

  12. 在数据库中查询秒杀商品,并将库存减一。如果减一后的库存小于等于零,则在redis中标记商品id+“isStockEmpty”。

  13. 生成订单和秒杀订单,插入数据库。将秒杀订单存入redis。

  14. 返回结果0,访问"/seckill/result"接口,返回的是订单id。首先查询redis,若已经将该商品标记为库存空,返回-1,意为秒杀失败,已经抢购一空,在数据库的秒杀订单查找该商品该用户,若无,则返回0,排队中,若存在,返回订单id,秒杀成功。

  15. 若返回结果0,轮询。通过setTimeout方法每隔50ms调用一次该方法。

  16. 秒杀成功跳转到订单详情页。并将返回的订单id通过路径传入。

  17. 访问"/order/detail"接口,查询订单信息显示在页面。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值