一个可实际用于生产环境的秒杀系统究竟该如何设计?

前言

之前为了在简历上添些东西,也做过秒杀系统,但仅仅是Demo级别,仅仅考虑了如何减轻数据库压力,如何防止超卖,对于很多在秒杀系统中需要注意的问题实际上并没有去完善,所以当面试官问到的时候,回答常常不能让面试官满意。

今天就来梳理一下秒杀系统究竟要注意什么,该如何去优化。同时希望能够针对这种高并发的场景,提炼出自己的一套基础的方法论,提升自己思考问题的深度,广度以及架构能力。

这次,让我们从问题出发,来进行秒杀系统的设计。

秒杀系统要解决什么问题?

  1. 秒杀系统是一个非常典型的高并发场景,时间极短,瞬间用户量极大,在这种场景下,我们的服务器,我们的数据库是非常脆弱的。
    如果每秒数万的请求打到我们的服务器上,后端数据库几乎肯定会宕机,我们的缓存同样也要考虑缓存穿透,缓存击穿,缓存雪崩的问题。
  2. “超卖”问题。所谓超卖,就是指我们本想只秒杀100件,结果卖出去1000件,给商家造成了损失,这事关钱的问题,所以也是一个大问题。简单来说的话其实就是秒杀商品的库存该如何维护?
  3. 服务治理相关的的问题。我们的秒杀系统,显然是隶属于某个大系统,比如说商城系统的,我们希望秒杀系统即使出了问题,也不会影响到整个大的业务,说白了就是如何进行服务的熔断与降级?
  4. 安全与公平问题。我们如何保证秒杀的公平性呢,即保证不会有“专业”的秒杀团队来通过脚本抢到了所有的商品,另外,秒杀的下单url我们如何保密,防止某些能够提前得到url的消费者提前下单呢?

该如何设计?

让我们一个问题一个问题来看:

  1. 高并发带来的问题:这个问题,可以细分为对业务逻辑服务器的压力,对缓存的压力,以及对数据库的压力。
    对于业务逻辑服务器的压力,我们的解决方法就是加机器,多部几台服务器,同时用我们的反向代理神器Nginx来进行请求分发、负载均衡,同时过滤掉一些恶意的请求,我们对请求的响应能力就得到了大大的提升。
    对于缓存也是同样的方法,我们的Redis做集群,主从同步,读写分离,因为后续我们会用Redis来做业务逻辑,因此也要加上哨兵模式和持久化,保证缓存的高可用。
    另外,对于一些热点商品的商品信息等这些不会改变的数据,我们可以在每一个业务服务器的内存中做一个更高级的缓存,这样比Redis要来的更高效,同时我们只保存不会改变的信息,也就不用去处理复杂的缓存一致性问题。
    对于数据库,实际上数据库是我们的最大瓶颈所在,因为秒杀场景实际上是大量流量对某一个资源(库存)的争夺,而且是更新而非读取,单纯的读写分离并不能解决问题。我们只能从上游减少打到数据库的请求量。显然我们的秒杀场景,最终能秒杀到的是少数,如果库存只有1000条,我们希望只有1000条请求打到MySQL上,这样显然能大大减轻数据库的压力,同时我们用一个MQ将数据库解耦出来,去异步的平稳的操作数据库,也能起到保护数据库的作用。
    总结来说的话,对于这种瞬时的大量流量,我们很自然的想法就是搞一个MQ来削峰,异步的操作数据库。同时,进行数据的预热,在秒杀开始前就准备好相应的数据到缓存中。

  2. 如何维护库存来防止超卖呢?一个很自然的想法是我们在扣减库存之前做个判断,当仍有库存时我们才会扣减库存,订单转到下一个状态。 问题是高并发的场景下,可能在我们判断时仍有库存,但扣减时已经没有了。解决这个问题最直观的方法就是加个锁,那么有没有更好的办法呢?其实我们只是想将判断和扣减库存作为一个原子操作来进行,保证判断后到扣减库存的过程没有其他的资源争夺。这可以通过Redis来实现,因为Redis的lua脚本天然就是原子性的。同时,将库存维护在Redis中显然要比维护在数据库中更高效。

  3. 服务治理相关:这个就是要维护秒杀服务的相对独立性,秒杀系统使用单独的数据库与缓存,与其他业务隔离开,其实就是微服务的单一职责原则,这样在机器层面避免了秒杀业务对其他业务的影响。
    同时,进行服务的熔断,限流,降级,感觉要撑不住了,通过一个分布式开关,开启限流,再不行就要降级,再不行,就只能放弃秒杀服务,直接熔断,防止影响其他服务。

  4. 安全问题:拦截恶意请求。对于url的暴露问题,我们可以将url动态化,进行MD5加密。

你可能感兴趣的:(中间件,架构设计)