比如,微博中肯定是发微博的用户比看微博的人要少很多很多。这个时候,对于系统而言,整体流量就会不太大,而写流量很可能只占到总体的 1% 。这样的话,即使我们系统 QPS 达到了 10000次/s …

比如,微博中肯定是发微博的用户比看微博的人要少很多很多。这个时候,对于系统而言,整体流量就会不太大,而写流量很可能只占到总体的 1% 。这样的话,即使我们系统 QPS 达到了 10000次/s ,那写请求每秒也只有100 次,所以花大的精力去优化写请求是没有必要的,对于业务并没有什么影响。
但是,如果是对于突如其来的超大流量,可能就会出现高并发的写请求的场景,例如最经典的秒杀场景。如果我们的商城在双十二零点要搞一个秒杀活动,限制前 200 个用户,那么在秒杀活动即将开始之前,就会有很多的用户疯狂的去刷新APP或者览器,为了就是不错过这次秒杀。
在这个时候,我们系统面临着的依旧是大量的读请求,那我们应该怎么去应对呢?

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

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

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

那我们如何使用消息队列来解决现在的秒杀场景带来的问题呢?下面我们就一起来看看该怎么使用。 秒杀削峰写流量
看过我前面的文章的朋友可能会问,为什么不像以前那样的进行分库分表呢?当然是可以的,但是你应该知道不管是分库分表还是扩展数据库,势必会增加复杂性的,还要做数据迁移,虽然你通过前面的学习能具备解决这些数据复杂性的问题。
但是,这样的秒杀场景,高并发的写请求并不是持续的,也不是每天都有的,可能只有活动的几秒或者几十秒就结束了,就没那么大的并发写请求了。如果我们为了这几十秒的并发写请求折腾好几天来搞数据,不现实,没那么多时间也没那么多资源给我们去折腾。
所以,我们就可以将秒杀请求暂时存在消息队列中,然后我们的业务服务器高速用户“秒杀进行中”等,等释放系统资源后再去处理其他用户请求。
我们在后台可以开启 n 个队列处理程序,不断的消费消息队列中的任务,然后校验库存接着下单等操作,现在由于我们是有限的队列处理线程在执行,所以最终落到数据库上的并发请求也是有限的。用户请求是可以在消息队列中短暂堆积的,当库存为零了,消息队列堆积的请求也就可以全部释放了。 7f0c974ba0d0fa9e2e2177dfb991f44f.png
如上所述,就是消息队列在秒杀系统中最关键的运用:削峰填谷,即用来削平短暂的流量峰值。
注意,我们秒杀过程中不能长时间的不给用户响应,只能短暂的延迟通知结果,你想想看,如果你正在秒杀我们一个商品的时候,我个把小时都不告诉你,你还在傻傻等着结果,你心里肯定在怀疑我们秒杀的真实性,是不是套路了你。因此,我们在使用消息队列应对流量高峰时,需要对队列的处理时间,前段写入流量的大小以及数据库处理能力都要做好评估,最后根据不同量级来决定该部署多少台处理程序。
例如,我们现在有 1000 个商品参与秒杀,单次购买请求的时间大概在 500ms ,那么秒杀总共时间就是 500s ,此时,如果我们部署 10 台队列处理程序,则秒杀的处理请求时间也就在 50s ,也就是说,用户需要等待 50s 才可以看到此次的秒杀结果,对于常理来说,这个时间用户是完全可以接受的。而据库端也只有 10 个并发打过去,也是没有压力的。 异步化简化秒杀业务
通过上面的学习我们知道了消息队列能够在大流量的写请求时,起到削峰填谷的作用。其实,我们还可以使用异步化机制来简化我们的秒杀请求业务流程,以提升我们整个系统性能。
如上我们秒杀场景下,在处理一个购买请求时,需要耗时 500ms ,其实,我们整个流程中是有主次之分的,也就是说有些次要的流程可以不和当前购买主流程同步在一起的。比如,我们当前购买主流程是创建订单和扣减库存,而非关键流程是下单成功之后的发放优惠券和增加用户积分等操作。
假如,发放优惠券耗时 50ms,增加用户积分耗时 50ms,现在如果我们将这两个操作放在另一个队列处理机中去执行,那么整个购买流程是不是就缩短到了 400ms了,性能提升了 20% 。 3c8223fbd435dd1492b20f58e1a1d32d.png 解耦实现秒杀系统模块间松耦合
消息队列除了上面表现出的削峰填谷和异步机制,同时,它在秒杀中还有另外一个作用那就是解耦。
假如,现在咱们公司的大数据团队对我们一个需求就是,他们想在我们在秒杀活动之后做相关数据统计,用来分析活动商品的受欢迎度、购买用户的行为特点以及对于秒杀互动的满意程度等相关指标。这个时候,我们就需要将大量的数据发送给大数据团队,该怎么做呢?
我们最容易想到的方案就是,使用 HTTP 或者 RPC 的方式来同步调用,即大数据团队提供一个接口给我们,然后我们将需要的数据推过去,但是,这样做会有两个问题:
    整个系统耦合较高,如果他们的接口出问题就会直接影响到了我们秒杀系统的可用性。     如果大数据团队接口相关参数要变更的话,我们这边也得跟着变更。
这个时候,我们就可以使用消息队列来对其进行解耦。
    秒杀系统产生一条购买数据之后,我们先将全部数据发送到消息队列中。     然后大数据团队自己订阅消息队列的topic。     最后他们自己做数据处理方面工作。
如此一来,大数据系统的故障就不会影响到我们秒杀系统了,同时,当他们需要更新相关字段的话,只需要解析消息队列中的数据,拿到自己需要的数据就行了。 776cdab5995413259647dc5b22de865f.png
削峰填谷、异步处理以及解耦是消息队列在秒杀系统设计中起到至关重要的作用。
    削峰填谷可以削掉到达秒杀系统的峰值流量,让业务逻辑处理更加缓和自然;     异步处理可以简化整个业务流程的步骤从而提升系统性能;     解耦合可以将秒杀系统和大数据系统解耦开,这样彼此间的任何变更都不会影响到对方。
总结,今天我们结合秒杀的这种实际场景,一起学习到了消息队列在高并发系统设计中起到的作用。主要讲到这三大点:
    削峰填谷是消息队列最主要的作用,但是会造成请求处理的延迟。     异步处理是提升系统性能的神器,但是你需要分清同步流程和异步流程的边界,同时消息存在着丢失的风险,我们需要考虑如何确保消息一定到达。     解耦合可以提升你的整体系统的鲁棒性。
当然,你要知道,在使用消息队列之后虽然可以解决现有的问题,但是系统的复杂度也会上升。比如上面提到的业务流程中,同步流程和异步流程的边界在哪里?消息是否会丢失,是否会重复?请求的延迟如何能够减少?消息接收的顺序是否会影响到业务流程的正常执行?如果消息处理流程失败了之后是否需要补发?这些问题都是我们需要考虑的。

消息队列的深入应用

一、消息队列选型

1.1 主流消息队列对比

1
RabbitMQ
  • 特点:成熟稳定,功能丰富
  • 优势:支持多种消息模式,管理界面友好
  • 适用场景:对可靠性要求高的场景
1
Kafka
  • 特点:高吞吐量,分布式流处理
  • 优势:性能优异,支持大规模数据流
  • 适用场景:日志收集、大数据处理
1
RocketMQ
  • 特点:阿里开源,适合电商场景
  • 优势:事务消息,顺序消息
  • 适用场景:电商、金融等对顺序和事务要求高的场景
1
ActiveMQ
  • 特点:Apache项目,支持多种协议
  • 优势:跨语言支持好
  • 适用场景:多语言环境

二、消息队列的核心问题

1. 消息可靠性

1
消息丢失的常见原因:
  • 生产者发送失败
  • 消息队列服务故障
  • 消费者处理失败
1
解决方案:
  • 生产者确认:使用事务或确认机制
  • 持久化:消息持久化到磁盘
  • 消费者确认:处理完成后确认消息
  • 重试机制:失败后自动重试

2. 消息重复

1
产生原因:
  • 网络重传

  • 消费者重复消费

  • 系统故障恢复

  • 幂等性设计:业务逻辑保证幂等

  • 去重机制:使用唯一ID去重

  • 状态检查:处理前检查是否已处理

3. 消息顺序

1
顺序消息的挑战:
  • 多消费者并发处理

  • 消息分区和路由

  • 失败重试的顺序

  • 单分区单消费者:保证顺序但影响性能

  • 消息路由:相同key的消息路由到同一分区

  • 顺序消费:消费者按顺序处理消息

4. 消息延迟

1
延迟的原因:
  • 消息堆积
  • 处理速度慢
  • 网络延迟
1
优化方案:
  • 增加消费者:提高处理能力
  • 批量处理:提高处理效率
  • 优先级队列:重要消息优先处理
  • 延迟消息:支持延迟投递

三、高级应用场景

1. 分布式事务

1
两阶段提交(2PC)
  • 使用消息队列实现分布式事务
  • 保证多个服务的数据一致性
  • 适用于对一致性要求高的场景
1
最终一致性
  • 通过消息队列实现最终一致性
  • 牺牲强一致性换取性能
  • 适用于大多数业务场景

2. 事件驱动架构

1
事件溯源
  • 使用消息队列记录所有事件
  • 通过事件重建状态
  • 支持时间旅行和审计
1
CQRS(命令查询职责分离)
  • 命令通过消息队列异步处理
  • 查询从读库读取
  • 读写分离,提高性能

3. 微服务通信

1
服务解耦
  • 服务间通过消息队列通信
  • 降低服务间的直接依赖
  • 提高系统的灵活性
1
服务发现
  • 通过消息队列实现服务发现
  • 动态的服务注册和发现
  • 支持服务的动态扩展

四、监控和运维

4.1 关键指标

  1. 消息堆积:队列中未处理的消息数量
  2. 处理速度:每秒处理的消息数
  3. 延迟时间:消息从生产到消费的时间
  4. 错误率:处理失败的消息比例
  5. 吞吐量:系统的整体处理能力

4.2 监控工具

  • Prometheus + Grafana:指标监控和可视化
  • ELK Stack:日志收集和分析
  • 消息队列管理界面:RabbitMQ Management、Kafka Manager等

4.3 告警策略

  • 消息堆积超过阈值
  • 处理速度下降
  • 错误率上升
  • 消费者异常

五、最佳实践

1. 消息设计

  • 消息大小:控制消息大小,避免过大
  • 消息格式:使用标准格式(JSON、Protobuf等)
  • 版本兼容:考虑消息格式的版本兼容

2. 消费者设计

  • 批量消费:提高处理效率
  • 并发控制:合理设置并发数
  • 错误处理:完善的错误处理和重试机制
  • 幂等性:保证消息处理的幂等性

3. 性能优化

  • 批量操作:批量发送和消费消息
  • 压缩:对消息进行压缩
  • 分区策略:合理设计分区策略
  • 资源调优:调整JVM、网络等参数

4. 安全考虑

  • 访问控制:严格的权限管理
  • 加密传输:使用TLS加密
  • 消息加密:敏感消息加密存储
  • 审计日志:记录操作日志

六、总结

消息队列是现代分布式系统的重要组成部分,它提供了削峰填谷、异步处理、系统解耦等核心能力。但在使用消息队列时,也需要考虑:

  1. 可靠性:如何保证消息不丢失
  2. 一致性:如何处理消息重复和顺序
  3. 性能:如何提高处理效率
  4. 可维护性:如何监控和运维

通过合理的设计和优化,消息队列能够为系统带来巨大的价值,帮助构建高性能、高可用的分布式系统。在实际应用中,需要根据具体场景选择合适的消息队列,并遵循最佳实践,才能充分发挥消息队列的优势。

本文标题: 消息队列的应用场

发布时间: 2019年10月26日 00:00

最后更新: 2025年12月30日 08:54

原始链接: https://haoxiang.eu.org/4f3719b8/

版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0许可协议,转载请注明出处!

× 喜欢就赞赏一下呗!
打赏二维码