秒杀系统个人总结

秒杀系统架构

秒杀系统是一个并发量要求高、负载均衡要求高的、业务场景简单但是逻辑稍微复杂的系统,所以经常会作为面试高级后端开发的面试题。主要考察的就是对问题的拆解、分析、解决,以及架构设计的能力。

基础架构

  • 客户端
    • web 浏览器 / app
  • 负载均衡层
    • Nginx
  • web 层
    • 接收 Http 请求
    • 做限流
      • 分布式限流
      • id 限流
  • service 层
    • 库存操作
    • 生成订单
  • 数据存储层
    • mq + mysql

客户端

服务端是一个潜在的考察点,还是有很多问题需要解决的。有些网上给出的设计方案没有对这块做详细考虑。

  • 客户端限流(在浏览器上行不通)

    客户端做一定控制来限流(比如概率),防止刷单,减少成功次数,显示在排队,实际上没发网络请求

  • 前端展示

    秒杀按钮展示要有个定时器,涉及到前后端时钟同步的问题

    • 校对时间差

      获取服务端时间,客户端时间 - 服务端时间,比较得到差值,用这种方法来同步。然而注意到网络是有开销的,这个开销需要想办法消除。否则这种毫秒级甚至秒级的时间差,会影响到秒杀的公平性。

      • 发送更轻量级的服务响应
      • 优化代码,使客户端和服务端计算时间的流程很短
      • 回调计算时间先执行

      如果这个同步是很长时间之前同步的呢,可能时间过了很久后已经相差较多了。

      • 定时同步,半小时一次

      如果客户修改系统时间怎么办

      • 记录客户端的周期时间序列(这个周期可以设置的短一些,比如10秒这样子),第一次为基准,先做客户端与服务端同步,得到差值 T
      • 在第二个周期,计算两个时刻的时间差,再减去周期时间,就是相差的时间,如果在某一个很小的范围内,说明没有问题,如果不在范围内,说明可能修改了时间,那么修改 T 为 T + 差值

web 层

这一层要考虑限流问题,以及防止恶意刷量的问题。首先限流要尽量在上层去做,以最大程度减少后端系统的压力。其次,要避免用户找到url,不停的大量发送网络请求,或者在活动前就发送,这样也是有问题的。

  • 限流

    这属于分布式限流,一般采用 redis 来做限流,可以用令牌桶来做

  • 防止提前刷 url

    这个可以在服务端根据系统时间来决定要不要处理,也可以用一个随机的网址来保证无法模拟 url 请求(这个点还比较模糊)
    而且这里涉及到服务端各服务器的时钟同步

  • 同一个 url

    这里可以用 redis 记录或者本地记录来进行计数过滤,保证用户每秒发送请求响应次数不超过一个阈值

service 层

  • 如果有库存,并且拿到了资源,再生单的逻辑顺序
  • 解决并发问题的思路
      • 悲观锁

        性能比较差

      • 乐观错

        性能好些

    • 缓存

      缓存来保存库存量,减少访问 mysql 带来的并发,用 redis 可以做到

      但是如果在拿到资格后出现问题,怎么办?在缓存里已经被减掉了,这时需要归还资格,否则卖出的数就会少,这个错误可能会出现在生单,订单入库的阶段,直到入库,这个资格才能算作彻底被消费掉

数据存储层

mysql 更合适,有唯一键的限制,hbase 存放海量数据

同步还是异步的问题

  • 同步

    好处,等待结果写入库里,完全闭环

  • 异步

    可能会写入失败,丢失订单信息,因为订单详情是要尽快展示给用户的,所以一旦失败,该取消这次秒杀的结果,还是继续认为成功,是比较棘手的问题。异步出错了,可能可以修复,但是也可能会一直出错,重试无效。这种应该归还,然后把结果通知给用户。如果异步默认生单成功,但是怎么也写不进去,那就会有问题了。(这块每太想清楚)

    先说说异步做法

    • 交由本地线程池处理

      占用 service 层资源

    • 发送 kafka

      减少了 service 层资源占用,但是要保证 kafka 可靠,这里需要保证 有副本,ack -> ALL,replica 设置>1

其他问题

  • 如何保证同一个用户只能下一个单

    • 如果是用 redis 来做
      • 那么可以在获取权限的前一层写入,做第一层判断
      • 也就是如果获取到一次资格,立马锁定,如果后面的生单失败了,再解锁
    • 如果用 mysql 来做
      • 会导致在最下层才判断出来,而前面已经拿到了资格,导致很多人没抢到,这时应该返回什么?一般返回失败,直到全局的秒杀结束,再通知结束。
  • 因为资源被占用后,后续不一定生单成功,所以如果资源没了,不应该直接展示秒杀结束

    • 要有一个全局的标识,确认秒杀结束
  • server 端时间同步

    • 服务启动时以某一个为基准就好,可以是集群中的,也可以是集群外的

猜你喜欢

转载自www.cnblogs.com/43726581Gavin/p/9066053.html