消息队列入门

消息模型

  • 队列模型
  • 发布-订阅模型(Publish-Subscribe Pattern)

队列模式和发布 - 订阅模式是并存的,有些消息队列同时支持这两种消息模型,比如 ActiveMQ。对比这两种模型,生产者就是发布者,消费者就是订阅者,队列就是主题,并没有本质的区别。它们最大的区别其实就是,一份消息数据能不能被消费多次的问题。

如何利用事务消息实现分布式事务

消息队列中的“事务”,主要解决的是消息生产者和消息消费者的数据一致性问题。
一个严格意义的事务实现,应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。
比较常见的分布式事务实现有 2PC(Two-phase Commit,也叫二阶段提交)、TCC(Try-Confirm-Cancel) 和事务消息。
事务消息适用的场景主要是那些需要异步更新数据,并且对数据实时性要求不太高的场景。
在 RocketMQ 中的事务实现中,增加了事务反查的机制来解决事务消息提交失败的问题。

如何确保消息不会丢失

分布式链路追踪系统
利用消息队列的有序性来验证是否有消息丢失
如果对消息的可靠性要求非常高,可以通过配置 Broker 参数来避免因为宕机丢消息。在收到消息后,将消息写入磁盘后再给 Producer 返回确认响应,这样即使发生宕机,由于消息已经被写入磁盘,就不会丢失消息,恢复后还可以继续消费。例如,在 RocketMQ 中,需要将刷盘方式 flushDiskType 配置为 SYNC_FLUSH 同步刷盘。
编写消费代码时需要注意的是,不要在收到消息后就立即发送消费确认,而是应该在执行完所有消费业务逻辑之后,再发送消费确认。

  • 在生产阶段,你需要捕获消息发送的错误,并重发消息。
  • 在存储阶段,你可以通过配置刷盘和复制相关的参数,让消息写入到多个副本的磁盘上,来确保消息不会因为某个 Broker 宕机或者磁盘损坏而丢失。
  • 在消费阶段,你需要在处理完全部消费业务逻辑之后,再发送消费确认。

如何处理消费过程中的重复消息

用幂等性解决重复消息问题,一般解决重复消息的办法是,在消费端,让消费消息的操作具备幂等性。
几种常用的设计幂等操作的方法

  • 利用数据库的唯一约束实现幂等
  • 为更新的数据设置前置条件
  • 记录并检查操作

消息积压如何处理

在消息的收发两端,我们的业务代码怎么和消息队列配合,达到一个最佳的性能。
要保证消费端的消费性能要高于生产端的发送性能,这样的系统才能健康的持续运行。
能导致积压突然增加,最粗粒度的原因,只有两种:要么是发送变快了,要么是消费变慢了。

开源代码该如何入手

  • 通过文档来了解开源项目
    通过看文档,快速地掌握这个软件整体的结构,它有哪些功能特性,它涉及到的关键技术、实现原理和它的生态系统等等。
  • 用以点带面的方式来阅读源码
    带着问题去读源码,最好是带着问题的答案去读源码。你每次读源码之前,确定一个具体的问题。

如何使用异步设计提升系统性能

异步思想就是,当我们要执行一项比较耗时的操作时,不去等待操作结束,而是给这个操作一个命令:“当操作完成后,接下来去执行什么。”

如何实现高性能的异步网络传输

序列化与反序列化:通过网络传输结构化的数据

序列化实现的时候,需要综合考虑数据可读性,实现复杂度,性能和信息密度这四个因素。

传输协议:应用程序之间对话的语言

在传输数据的的时候,首先要解决的就是断句问题。对于传输层来说,收到的数据是什么样的?就是一段一段的字节,但是,因为网络的不确定性,你收到的分段并不一定是我们发出去的分段。

  • 分隔符
  • 预置长度

所谓的单工通信就是,任何一个时刻,数据只能单向传输,一个人说的时候,另外一个人只能听。HTTP1 协议
在实际上设计协议的时候,一般不关心顺序,只要需要确保请求和响应能够正确对应上就可以了。这个问题可以这样解决:发送请求的时候,给每个请求加一个序号,这个序号在本次会话内保证唯一,然后在响应中带上请求的序号,这样就可以把请求和响应对应上了。这样就解决了双工通信的问题

“使用 ID 来标识请求与响应对应关系”的方法,是一种比较通用的实现双工通信的方法,可以有效提升数据传输的吞吐量。

内存管理:避免内存溢出和频繁的垃圾回收

垃圾回收完成后,还需要进行内存碎片整理,将不连续的空闲内存移动到一起,以便空出足够的连续内存空间供后续使用

垃圾回收是不可控的,而且是无法避免的。但是,可以通过一些方法来降低垃圾回收的频率,减少进程暂停的时长。使用过被丢弃的对象才是垃圾回收的目标,所以,我们需要想办法在处理大量请求的同时,尽量少的产生这种一次性对象。
对于需要频繁使用,占用内存较大的一次性对象,我们可以考虑自行回收并重用这些对象,来减轻垃圾回收的压力。

Kafka 如何实现高性能 IO

使用批量消息提升服务端处理能力
使用顺序读写提升磁盘 IO 性能
利用 PageCache 加速消息读写
ZeroCopy:零拷贝技术

缓存策略:使用缓存减少磁盘 IO

选择只读缓存还是读写缓存?唯一的区别就是,在更新数据的时候,是否经过缓存。
读写缓存的这种设计,它天然就是不可靠的,是一种牺牲数据一致性换取性能的设计。

正确使用锁保护共享数据,协调异步线程

锁的原理是:任何时间都只能有一个线程持有锁,只有持有锁的线程才能访问被锁保护的资源。
如果能不用锁,就不用锁;如果不确定是不是应该用锁,那也不要用锁。加锁和解锁过程都是需要 CPU 时间的,这是一个性能的损失。
只有在并发环境中,共享资源不支持并发访问,或者说并发访问共享资源会导致系统错误的情况下,才需要使用锁。
使用读写锁要兼顾性能和安全性

用硬件同步原语(CAS)替代锁

硬件同步原语(Atomic Hardware Primitives)是由计算机硬件提供的一组原子操作,我们比较常用的原语主要是 CAS 和 FAA 这两种。

数据压缩:时间换空间的游戏

数据压缩不仅能节省存储空间,还可以用于提升网络传输性能。
压缩它的本质是资源的置换,是一个时间换空间,或者说是 CPU 资源换存储资源的游戏。
在选择压缩算法的之前,用系统的样例业务数据做一个测试,可以帮助找到最合适的压缩算法。
如果要对流数据进行压缩,那必须把流数据划分成多个帧,一帧一帧的分段压缩。