团队研发文档

开发规范、技术文档等


异步消息性能提升学习

<h1>一、消息队列解决的问题</h1> <p>当进程中需要交互的两个模块性能差距很大时,会基于 FIFO 先入先出队列实现生产者消费者模型,通过调整生产者、消费者的数量,实现线程间的负载均衡。而且,生产者仅将任务添加至队列首部就可以返回,这种异步操作释放了它的性能。</p> <p><img src="https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/22175fae6e3fcb6e75dcce92cf76cc9a" alt="" /></p> <p>把单机中的 FIFO 队列放大到分布式系统中,就形成了独立的消息队列服务。此时,生产者、消费者的角色从线程变成了网络中的独立服务,生产者可以向消息队列发布多种消息,多个消费者也可以订阅同一种消息</p> <p><img src="https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/36f7af3251678986101fee0b06da9828" alt="" /></p> <h3>消息队列的优点</h3> <ul> <li> <p>降低了系统的耦合性。</p> </li> <li> <p>可伸缩性很容易实现。</p> </li> <li> <p>天然实现“削峰填谷”功能。 消息队列服务会将消息持久化存储在磁盘中,在高峰期来不及处理的消息,会在低谷期被消费者服务处理完。通常,消息队列会使用廉价、高容量的机械磁盘存放消息,可以轻松缓存住高峰期超载的全部请求。</p> </li> <li> <p>提高了系统可用性。 首先,持久化到磁盘中的消息,在宕机故障时比内存中的请求有更高的可用性;其次,消息队列可以隔离故障,比如,消费者服务宕机后,生产者服务短期内不会受到影响;再次,当总吞吐量超过性能上限时,还可以设置不同的消息优先级,通过服务降级保障系统的基本可用性。</p> </li> <li> <p>消息队列的生产者天然具备异步功能。 降低了生产者的请求处理时延,提升了用户体验。当消息队列作为通讯方式时,这种“事件驱动”的分布式系统很容易通过消息实现服务拆分,成本会低很多。</p> </li> <li>消息队列服务对于各种消息的发布、消费情况都有统计 从消息中就能获得业务的实时运行状态,以极低的成本实现系统的监控。</li> </ul> <h3>消息的产生、消费在时间上是连续的</h3> <ul> <li> <p>首先,在网络通讯中,很容易通过批量处理提高网络效率。比如生产者快速发布消息时,Kafka 的客户端 SDK 会自动聚集完一批消息,再一次性发送给 Broker,这样网络报文的有效载荷比会很高。</p> </li> <li> <p>其次,在数据写入磁盘的过程中,由于时序性特征,存放消息的文件仅以追加形式变更,这样多数情况下机械硬盘的磁头仅朝一个方向转动,这让磁盘写入速度可以轻松达到 100MB/s。</p> </li> <li>最后,由于消费者也是按照 FIFO 规则有序接收消息的,这样消息队列的缓存就可以通过批量预读等优化方式,大幅提高读操作的缓存命中率。</li> </ul> <h1>二、消息队列保证服务质量</h1> <p>首先,虽然生产者会异步地发布消息,但毕竟需要接收到消息队列的确认,才构成完整的发布流程。网络传输是相对漫长、不可控的,所以在高性能场景中,生产者应基于多线程或者非阻塞 Socket 发布消息,以提高并发能力。</p> <p>其次,当消费端性能不足需要扩容时,必须同步增加消息队列服务中的队列(在 Kafka 中叫做分区),才能允许新增的消费节点并行接收消息,提高消息的处理能力。否则,当多个消费者消费同一消息队列时,消息的有序性会导致多个消费节点串行处理消息,无法发挥出它们的全部性能 <img src="https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/95444745f7f0d3f84500eb8f800f7385" alt="" /></p> <p>最后,如果通过监控发现消息的消费能力小于生产能力,那就必须及时扩容消费端,或者降低消息的发布速度,否则消息就会积压,最终导致系统不可用。</p> <h3>消息队列的 QoS(Quality of Service)是如何保证消息在传递过程中会不会丢失,以及接收方会不会重复消费消息。</h3> <p>给消息队列定义了三种 QoS 级别: at most once,每条消息最多只被传送一次,这意味着消息有可能丢失; at least once,每条消息至少会传送一次,这意味着消息可能被重复消费; exactly once,每条消息恰好只传送一次,这是最完美的状态。</p> <p>需要 at most once 约束的场景较罕见,因此目前绝大部分消息队列服务提供的 QoS 约束都是 at least once,它是通过以下 3 点做到的: 1.生产端发布消息时,只有消息队列确定写入磁盘后,才会返回成功 2.为防止消息队列服务出现故障后丢消息,我们也需要将数据存放在多个副本节点中。 3.消费端必须在消费完消息(而不是收到消息)后,才能向消息队列服务返回成功。</p> <p>这样,消息队列就能以很高的可用性提供 at least once 级别的 QoS。 而 exactly once 是在 at least once 的基础上,通过幂等性 idempotency 实现的。对于一条“幂等性消息”,无论消费 1 次还是多次,结果都是一样的。</p> <p>因此,Kafka 通过消息事务和幂等性约束实现了exactly once 语义,其中,发布消息时 Kafka 会创建全局唯一的递增 ID,这样传输消息时它就能低成本地去除重复的消息,通过幂等性为单队列实现 exactly once 语义;针对生产者向多个分区发布同一条消息的场景,消息事务通过“要么全部成功要么全部失败”,也实现了 exactly once 语义。</p> <h4>虽然消息队列自身拥有优秀的性能,但若想提高使用效率,我们就需要确保在生产端实现网络传输上的并发,在消费端扩容时同步增加队列或者分区,并且需要持续监控系统,确保消息的生产能力小于消费能力,防止消息积压。</h4>

页面列表

ITEM_HTML