Skip to content

solthx/secKill

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

本次的项目实践是使用 SpringBoot+Mybatis 对电商项目中秒杀模块的实现. 目前阶段使用4台云服务器来做分布式集群,提高并发处理性能.

后端技术

  • SpringBoot

  • Mybatis

  • Redis

  • Rocketmq

使用到的第三方工具

  • Mybatis-generator

  • joda-time

  • lombok

  • guava

集群拓扑图

部署环境为4台服务器,一台作为nginx反向代理服务器,两台作为WebServer服务器,一台作为数据仓库, 拓扑图如下:

pic

架构层次图

pic

接入层

  • 接入层模型 ViewObject:

    • View Object与前端对接的模型, 隐藏内部实现,供展示的聚合类型
  • 业务层 Model:

    • 领域模型,贫血+调用服务来提供输出能力
  • DataObject

    • 为领域模型的具体存储形式,一个领域模型由一个或多个DataObject组成,每一个DataObject对应一张表,以ORM方式操作数据库.
  • 自定义异常类属性:

    • errCode: 业务逻辑错误码 ( 如:1开头的错误码为通用错误,2开头的错误码为用户相关错误,3开头的错误码为交易信息相关错误等.. )

    • errMsg: 错误提示

服务层

  • 用户模块:

    • 注册(使用手机获取验证码短信来注册)
    • 登陆
  • 交易模块:

    • 下单操作
  • 商品模块:

    • 创建商品
    • 展示商品
  • 促销模块

    • 维护促销商品列表

优化手段:

  • 使用Redis集中式缓存解决Session一致性问题

    • 问题:分布式部署时,session存储在Web服务器端,下次用户请求过来,通过负载均衡,可能会分发到其他服务器节点,导致了session不一致问题
    • 解决方法:
      • 使用redis来做一个集中式缓存,将session存放到redis中,每次请求过来时,会从同一个redis节点中读取session信息
  • 使用多级缓存提高查询性能

    • 技术点:设置多级缓存,减少对数据库的访问次数,对于本地热点缓存,通过设置过期时间来保证数据库和缓存的数据一致性。
    • 本地热点数据缓存
      • 特点:
        • 使用的是JVM的缓存来对热点数据进行存储;因为当数据库对缓存中的数据进行修改后会造成脏读,所以本地缓存的过期时间要设的尽可能的短.
      • 第三方库:
        • Guava:
          • 提供了这样一种可控制大小和超时时间的线程安全的Map——即Guava Cache
          • 可配置lru策略(置换算法配置)
    • Redis集中式缓存
  • 下单接口的优化

    • 技术点:将一些写操作从数据库移动缓存中,使用异步消息队列保证缓存和数据库的最终一致性。
    • 用户风控, 活动校验等环节能用读缓存解决的就尽量不要读数据库.
    • 库存的行锁优化
      1. 将扣减库存操作放到redis中操作:

        • 预先将库存信息刷入redis中
        • 落单后在redis中减库存
        • 使用异步消息队列Rocketmq异步同步数据库内,从而保证库存数据库最终一致性保证
      2. 问题的发现与解决:

        • 使用消息队列异步同步数据库出现问题时,仍然无法保证缓存和数据库的一致性,例如:
          • 异步消息发送失败
          • 扣减操作失败
          • 下单失败无法正确补回库存
        • 解决办法,将 “创建订单事务提交成功” 和 “消息发出” 这两个动作再次绑定成一个事务,即可解决,也就是说,发一条事务型消息。
      3. 问题的发现与解决:

        • 遇到问题:
          • 在定期checkLocalTransaction的UNKNOWN状态的消息时,仅以当前传入消息的参数还不够(仅传入了ItemId和扣减个数amount)。
        • 解决问题:引入库存流水:
          • 数据类型:
            • 主业务数据(例如ItemModel)
            • 操作型数据(记录某个操作,便于追踪该操作的状态,保证异步操作的正确执行(例如具备回滚的能力等等)), 例如配置,中间状态的记录,等等
      4. 问题的发现与解决:

        • redis缓存在某些时刻仍无法保证与数据库一致性, 例如:
          • 在缓存中扣减之后,出现异常,回滚之后,缓存里的值并没有被回滚.
          • 在消息队列中,若线程在createOrder之后挂掉了,既无法进入catch代码块,也没有跳出try块,消息的状态将用于处于UNKONWN的状态..
        • 问题的解决:
          • 严格的缓存, db一致性将是以时间开销为代价的,因此,如果仅仅是保证缓存与数据库的最终一致性,可以根据具体的业务场景来决定高可用技术的实现,例如在本次的秒杀场景中的原则为:宁可少卖,不能超卖。
          • 解决方法:
            • 因为宁可少卖,不能多卖的业务场景,可以允许redis比实际数据库中少.
            • 超时自动释放(若订单流水在一定时间内没有从unkonwn状态转变,那么将会将此订单作废)
            • 分布式锁(日后看
      5. 库存售罄

        • 加库存售罄标示;售罄后不去操作后续流程;售罄后通知各系统售罄;回补上新;
      6. 在多线程场景下保证“库存扣减”的安全,防止超卖

        • 在进行库存扣减的时候,使用分布式锁来保证线程安全.(本次使用)
        • Redis + Lua脚本实现CAS也能够保证线程安全.
  • 防刷下单接口:

    • 技术点:秒杀令牌
    • 秒杀令牌:
      • 原理:
        • 秒杀接口需要依靠令牌才能进入
        • 秒杀的令牌由秒杀活动模块负责生成
        • 秒杀活动模块对秒杀令牌生成全权处理,逻辑收口
        • 秒杀下单前需要先获得秒杀令牌
      • 解决的问题:
        • 防止机器人刷下单接口。
      • 具体实现:
        • 下单时,前端ajax代码的click操作中,在下单逻辑之前,增加一个生成秒杀令牌的操作,然后服务端根据userid, itemid, promoid生成对应的秒杀令牌字符串,存到redis中并返回给前端,然后前端使用这个令牌作为钥匙,来去下单,这就确保了下单操作是在网页端进行的。
      • 缺陷:
        • 当上亿用户在浏览器手动点的时候,瞬间并发量也是很惊人的, 会把redis爆破..解决方案为秒杀大闸
  • 下单接口限流:

    • 验证码错峰:

      • 包装秒杀令牌前置, 用数学公式生成验证码
    • 限流:

      • 借助Guava实现接口限流:

        • Guava:
          • 初始化:RateLimiter rl = RateLimiter.create(10);
          • 获取令牌: bool canGetToken = rl.tryAcquire() ;
      • Guava的限流方法是对令牌桶算法的实现:

        • 令牌痛算法:
          • 算法介绍:
            • 服务器端维护一个桶,每访问服务端一次桶内减少一个令牌,同时,每秒钟往桶内增加x个令牌.
          • 解决场景:限制某一秒流量的最大值,用于保护系统不会因浪涌流量而发生崩溃。
      • 其他限流方法:

        • (额外补充)漏桶算法:
          • 算法介绍:
            • 服务端维护一个桶,桶内最多只能有k个令牌,每访问服务端一次桶内增加1个令牌,每秒桶内减少1个令牌。
          • 解决场景:希望网络流量以平滑平稳的方式流入,对于浪涌流量应对很差.
        • 线程池/信号量也可以进行一定程度的限流

About

手机销售平台电商项目

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages