消息系统

记录下最近对消息系统的思考和理解。

目的

消息系统存在的目的在于:解耦异步消峰

解耦

  • 依赖关系:组件之间从强依赖变为弱依赖,事件触发通过消息异步同步到依赖组件。
  • 时效:消息系统是一种暂存数据的异步系统,触发的时效是无法保证的。

异步

  • 单工:对于生产者和消费者来说,消息系统只是一种单工代理,生产者和消费者无法之间通信。
  • 回调: 异步意味着调用结果无法实时返回,调用结果需要通过配置回调来返回。

消峰:

  • 队列:队列的实质是无序变有序、多路变单路,从而降低依赖方的请求压力。
  • 结果:消峰只是一种手段,数据还是需要返回给请求方,可以通过回调或消息的方式返回。

原理

消息系统可分为代理(Broker)生产者(Producer)消费者(Consumer)

消息

消息是数据的载体,用于生产者(Producer)消费者(Consumer)沟通的媒介。

数据隔离

  • 主题(Topic):用于区分不同业务类型的消息,隔离的是不同的业务事件。
  • 分组(Group):用于区分同一主题下的不同业务方,隔离的是同一主题的不同订阅方。

代理

代理(Broker)是用于连接生产者(Producer)消费者(Consumer)的关键模块。

隔离方式

代理(Broker)的数据隔离可以分为物理隔离逻辑隔离

  • 物理隔离:订阅系统数量和数据备份数量相同,订阅数据之间隔离,缺点明显,浪费存储空间。
  • 逻辑隔离:数据仅有一份,不同系统通过偏移量(Offset)来记录消费进度。

逻辑隔离的优点在于数据仅此一份,不同业务系统消费同一份数据,也不会出现数据不一致的情况。

分组管理

分组管理指的是同一Topic下同一Group下不同消费者的管理。

每一个Topic都会存在多个消费队列,每个消费队列仅能对应一个消费者。

为了维护队列消费进度的Ofsset,不允许并发修改Offset的情况,需要保证队列 : 消费者 = 1 : 1

  • 代理维护:代理来管理消息队列的分配。
  • 消费者维护:消费者通过选举选择Leader节点用来管理消息队列的分配。

存储

存储方式可以采用传统的BTree索引存储文件存储两种方式。

  • BTree存储的查询效率高,但写入效率低。
  • 文件存储的查询效率低,但写入效率高。

考虑到消息的顺序读写,Kafka采用文件存储存储方式,追加写入日志。

Kafka的存储采用了分区(partition)分段(LogSegment)稀疏索引来实现高性能。

分区

数据分区是指数据分布在不同物理机上,可以分为两种方式:逻辑分区物理分区

  • 物理分区:数据备份的单位是物理机,物理机备份方式采用主从备份。
  • 逻辑分区:数据备份的单位是数据分片(Partition),分片是按照一主多从的方式来备份。

数据分片(Partition)的备份与物理机不同,采用数据分片(Partition)的备份不需要关心的物理机

Kafka采用了逻辑分区,RocketMQ才用了物理分区。

路由

在集群环境下,路由是用于快速的定位和发现目标消息所在的位置。

kafka中,通过选举功能选择一个Broker作为Leader,负责管理整个集群中TopicBrokerPartition

RocketMQ中,NameServer是负责管理整个集群TopicBroker信息。

RocketMQ未使用Zookeeper,所有Broker的注册都是基于NameServer

生产者

生产者(Producer)是消息的产生者,需要指定消息对应的主题(Topic)

发送方式

  • 同步发送:同步调用代理端,生产者可以感知消息发送的成功与否,确保此次消息的发送。
  • 异步发送:异步调用代理端,生产者无法感知消息发送的成功与否,在发送失败时仅能通过固定次数的重试。
  • 批量发送:也是一种异步发送,但是通过积累更多的消息一次性发送到代理端,主要用于大量消息的场景。

事务消息

事务消息是用来解决分布式事务的一种方式,它仅保证了消息的消费,如果出现消费失败是无法回滚,只能通过人工介入处理。

为了实现事务消息,生产者必须保证消息发送到代理端,那么只能选择同步方式来实现。

消费者

消费者(Consumer)是消息的订阅方,需要指定消息对应的主题(Topic)和消费者的分组(Group)

消费方式

  • 被动Push:代理端主动推送消息,消费者只需要被动等待,代理端集中管理消息的分发。
  • 主动Pull:消费者主动拉取最新的消息,基于轮询的方式。

    主动Pull是消息系统常见的消费方式,主动拉取消息减少了代理端的复杂度。

主动Pull的优点在于把数据分发的压力转移到消费者。

消费进度

上面提到,消费进度是通过Offset来维护的,Offset的上报决定了消息的消费情况。

实时上报:消费者处理完消息后立刻上报最新的Offset情况。

周期上报:消费者处理完消息后会把消费情况写入缓存,消费者会定期上报最新的Offset情况。

实时上报是同步方式,而周期上报是异步方式。

同步方式意味着阻塞,吞吐量不如异步方式。

事务支持

为了保证消息的成功消费,事务消息需要采用实时上报的方式。

异步意味着不能实时上报,如果上报失败是无法感知的。

顺序消费

前面提到一个主题可能包括多个消费队列,多个消费队列之间是无法保证消费顺序的。

为了实现顺序消费,需要业务方按照一定规则把一类消息存储在同一个队列中,从而保证消息被同一个消费者消费。

但是,如果发生消费者组重新分配的情况会导致顺序消费失效。

重复消费

造成重复消费的两种情况:

  • 异常宕机导致Offset未上报
  • 消费者线程执行超时无反馈

上面两种情况均是由于Offset没有及时上报,这是无法避免的,所以需要消费者做好幂等处理。