各位志同道合的大家好,我是踏上一线互联网十多年的代码爱好者。现在分享我们的各种经验和结构的实战。如果大家喜欢的话,请关注我,一起深入学习技术学。所有共享的结束将预示下一个主题。

之前几天一直在谈论如何提高我们系统的性能,将数据库转换为分布式存储,还谈到了各种缓存的原理和我们生产中使用的技术。因为事实上,我们大部分的工作都是读写较少的场景。

比如,微博中肯定是发微博的用户比看微博的人要少很多很多。这个时候,对于系统而言,整体流量就会不太大,而写流量很可能只占到总体的 1% 。这样的话,即使我们系统 QPS 达到了 10000次/s ,那写请求每秒也只有100 次,所以花大的精力去优化写请求是没有必要的,对于业务并没有什么影响。

但是,如果是对于突如其来的超大流量,可能就会出现高并发的写请求的场景,例如最经典的秒杀场景。如果我们的商城在双十二零点要搞一个秒杀活动,限制前 200 个用户,那么在秒杀活动即将开始之前,就会有很多的用户疯狂的去刷新APP或者浏览器,为了就是不错过这次秒杀。

在这个时候,我们系统面临着的依旧是大量的读请求,那我们应该怎么去应对呢?

因为这个秒杀场景中,用户查询的是少量的商品,属于查询热点数据,我们就可以采用缓存策略啊,将用户请求放在服务之外,或者是将静态资源放CDN等等,这些方案之前都教给大家了,自行查看哈,比如(分布式缓存的高可用方案,我们都是这么做的,CDN加速技术,开发人员也必须要搞清楚)

当秒杀活动在零点准时开始之后 ,就会有大量的用户瞬时向我们商城系统提交订单,扣减库存,这个时候用户的这一操作是不经过缓存的,而是直接落到数据库中的。1 秒内,会有 1 万个数据库连接产生,这个时候数据库就会很快崩溃,那我们该怎么办呢?一般这里我们会使用一个组件那就是消息队列。

消息队列是什么

消息队列的概念以及有什么作用,前面有讲到相关中间件的时候提到过(消息中间件能干什么?RabbitMQ、Kafka、RocketMQ正确选型姿势)。其实它就是一个暂时存放数据的容器,同时是一个平衡高速系统和低速系统处理任务时间差的工具,在系统设计中也是个比较常见的组件,比如,Java线程池会使用一个队列来存提交的任务,RPC 框架中,会将请求写到队列里,通过工作线程去处理。

那我们如何使用消息队列来解决现在的秒杀场景带来的问题呢?下面我们就一起来看看该怎么使用。

秒杀削峰写流量

看过我前面的文章的朋友可能会问,为什么不像以前那样的进行分库分表呢?当然是可以的,但是你应该知道不管是分库分表还是扩展数据库,势必会增加复杂性的,还要做数据迁移,虽然你通过前面的学习能具备解决这些数据复杂性的问题。

但是,这样的秒杀场景,高并发的写请求并不是持续的,也不是每天都有的,可能只有活动的几秒或者几十秒就结束了,就没那么大的并发写请求了。如果我们为了这几十秒的并发写请求折腾好几天来搞数据,不现实,没那么多时间也没那么多资源给我们去折腾。

所以,我们就可以将秒杀请求暂时存在消息队列中,然后我们的业务服务器高速用户“秒杀进行中”等,等释放系统资源后再去处理其他用户请求。

我们在后台可以开启 n 个队列处理程序,不断的消费消息队列中的任务,然后校验库存接着下单等操作,现在由于我们是有限的队列处理线程在执行,所以最终落到数据库上的并发请求也是有限的。用户请求是可以在消息队列中短暂堆积的,当库存为零了,消息队列堆积的请求也就可以全部释放了。

如上所述,就是消息队列在秒杀系统中最关键的运用:削峰填谷,即用来削平短暂的流量峰值。

注意,我们秒杀过程中不能长时间的不给用户响应,只能短暂的延迟通知结果,你想想看,如果你正在秒杀我们一个商品的时候,我个把小时都不告诉你,你还在傻傻等着结果,你心里肯定在怀疑我们秒杀的真实性,是不是套路了你。因此,我们在使用消息队列应对流量高峰时,需要对队列的处理时间,前段写入流量的大小以及数据库处理能力都要做好评估,最后根据不同量级来决定该部署多少台处理程序。

例如,我们现在有 1000 个商品参与秒杀,单次购买请求的时间大概在 500ms ,那么秒杀总共时间就是 500s ,此时,如果我们部署 10 台队列处理程序,则秒杀的处理请求时间也就在 50s ,也就是说,用户需要等待 50s 才可以看到此次的秒杀结果,对于常理来说,这个时间用户是完全可以接受的。而数据库端也只有 10 个并发打过去,也是没有压力的。

异步化简化秒杀业务

通过上面的学习我们知道了消息队列能够在大流量的写请求时,起到削峰填谷的作用。其实,我们还可以使用异步化机制来简化我们的秒杀请求业务流程,以提升我们整个系统性能。

如上我们秒杀场景下,在处理一个购买请求时,需要耗时 500ms ,其实,我们整个流程中是有主次之分的,也就是说有些次要的流程可以不和当前购买主流程同步在一起的。比如,我们当前购买主流程是创建订单和扣减库存,而非关键流程是下单成功之后的发放优惠券和增加用户积分等操作。

假如,发放优惠券耗时 50ms,增加用户积分耗时 50ms,现在如果我们将这两个操作放在另一个队列处理机中去执行,那么整个购买流程是不是就缩短到了 400ms了,性能提升了 20% 。

解耦实现秒杀系统模块间松耦合

消息队列除了上面表现出的削峰填谷和异步机制,同时,它在秒杀中还有另外一个作用那就是解耦。

假如,现在咱们公司的大数据团队对我们一个需求就是,他们想在我们在秒杀活动之后做相关数据统计,用来分析活动商品的受欢迎度、购买用户的行为特点以及对于秒杀互动的满意程度等相关指标。这个时候,我们就需要将大量的数据发送给大数据团队,该怎么做呢?

我们最容易想到的方案就是,使用 HTTP 或者 RPC 的方式来同步调用,即大数据团队提供一个接口给我们,然后我们将需要的数据推过去,但是,这样做会有两个问题:

  • 整个系统耦合较高,如果他们的接口出问题就会直接影响到了我们秒杀系统的可用性。
  • 如果大数据团队接口相关参数要变更的话,我们这边也得跟着变更。

这个时候,我们就可以使用消息队列来对其进行解耦。

  1. 秒杀系统产生一条购买数据之后,我们先将全部数据发送到消息队列中。
  2. 然后大数据团队自己订阅消息队列的topic。
  3. 最后他们自己做数据处理方面工作。

如此一来,大数据系统的故障就不会影响到我们秒杀系统了,同时,当他们需要更新相关字段的话,只需要解析消息队列中的数据,拿到自己需要的数据就行了。

削峰填谷、异步处理以及解耦是消息队列在秒杀系统设计中起到至关重要的作用。

  • 削峰填谷可以削掉到达秒杀系统的峰值流量,让业务逻辑处理更加缓和自然;
  • 异步处理可以简化整个业务流程的步骤从而提升系统性能;
  • 解耦合可以将秒杀系统和大数据系统解耦开,这样彼此间的任何变更都不会影响到对方。

总结,今天我们结合秒杀的这种实际场景,一起学习到了消息队列在高并发系统设计中起到的作用。主要讲到这三大点:

  • 削峰填谷是消息队列最主要的作用,但是会造成请求处理的延迟。
  • 异步处理是提升系统性能的神器,但是你需要分清同步流程和异步流程的边界,同时消息存在着丢失的风险,我们需要考虑如何确保消息一定到达。
  • 解耦合可以提升你的整体系统的鲁棒性。

当然,你要知道,在使用消息队列之后虽然可以解决现有的问题,但是系统的复杂度也会上升。比如上面提到的业务流程中,同步流程和异步流程的边界在哪里?消息是否会丢失,是否会重复?请求的延迟如何能够减少?消息接收的顺序是否会影响到业务流程的正常执行?如果消息处理流程失败了之后是否需要补发?这些问题都是我们需要考虑的。后面会继续这个话题来教大家怎么去解决这些问题。希望今天的内容能帮到你,谢谢。

下一篇预告:如何保证消息不丢失等相关问题

在公众号【架构师修炼】菜单中可自行获取专属架构视频资料,无套路分享,包括不限于 java架构、python系列、人工智能系列、架构系列,以及最新面试、小程序、大前端均无私奉献,你会感谢我的哈

相关推荐