面试复盘
Kafka
基础概念
- Broker:代表一个服务器实例
- Topic:消息的逻辑分类,和RocketMQ的Queue等同
- Partition:Topic的分片,用于并行处理Topic,是Topic下的物理队列,存储消息的offset,内部信息严格有序;单个partition分布在多个Broker上,每个partition有多个副本(不能超过Broker的数量或者说一个broker上不能有partition的两个副本用于冗余), 消息是否写入的根据就是有多少partition ack
- Leader/Follower:partition在多个Broker上有副本,其中一个是可读写的Leader Broker,其余是只读的Follower Broker
- Log Segment:每个partition是一个目录,下面就是多个日志文件段进行物理存储,以该segment的第一个offset命名
- ISR:In-Sync Replica,partition的多个Broker中有一个是leader,如果Leader宕机了需要从中选Leader(怎么做),一般是向Leader发送的心跳正常以及offset落后不超过30秒(可配置)
保证消息不丢失
核心点是:服务端只有在真正保证数据写入且有足够冗余时才能返回已写入
- Producer到broker:两个参数控制:min.insync.replicas=m和ack=n,即producer向partition写入消息时,partition需要保持有m个健康的broker,且至少有n个broker回复已经写入,该消息才算成功,这样可以保证:如果有机器宕机
- broker到consumer:手动提交offset,确保业务执行正确后commitSync防止漏消费,重复消费则业务端幂等即可
局部有序性
核心点是:利用服务端内部严格有序的结构,需要局部有序的数据送入同一个该结构中
为了保证高吞吐量和水平扩展采用的多broker并行架构注定了没办法保证全局有序,但其实业务也不需要全局有序,如创建订单→支付→发货→确认收货这样的流程,只需要保证单个订单的发送有序即可,kafka的partition严格有序,即使用分区器选择partition保证相同订单号进入相同的partition即可,须注意如果key分布不均会导致热点partition,可以增加partition数量、或自定义partitioner均衡分布,一般partition数量和消费者数量相等即可
- 分区选择:指定partition则直接发送,指定key则按keyhash,都没有则随机且尽量使用上一个partition
负载均衡
触发粒度是消费者组,一个Consumer Group订阅一个Topic,Topic中的同一个partition只能背一个Consumer消费,当Group成员变化或Topic增减partition时会发生rebalance,尝试最大化consumer对partition的利用程度,主要为:
- consumer加入、离开group
- Consumer心跳超时,被controller标记为离线
- Topic分区变化
- Consumer Group增减了订阅的Topic列表
重分配流程:
- 选举Group Leader负责收集小组信息并分组策略
- 向小组分发分组结果
- 停止消费执行策略结果
具体的分配主要有三个策略
- RangeAssignor:均衡分配,对每个主题,把其中的分区按consumer字典序依次发放,如果还有剩余则再从头发放,缺点就是如果主题较多,且无法整除的话前面的消费者就会比后面的多很多分区(如果订阅 100 个 Topic 且每个只有 1 个分区,第 1 个消费者将承担全部负载)
- RoundRobinAssignor:轮询分配,相比RangeAssignor,RRA把消费者组订阅的所有主题的所有分区摊开发牌,依次对每个consumer分配,缺点就是每个Topic的分布不均匀
- StickyAssignor:尽量保持原有分配,适合频繁扩缩容或者消费者不稳定,缺点是肯定没有Robin均衡
RocketMQ(4.x)
- Broker:与Kafka不同,由一主一从组成,Slave用于备份不对外提供服务,Master宕机须人工/脚本介入切换
- Nameserver:注册中心,管理元数据
- Topic:和kafka相同
- MessageQueue:相当于kafka的partition,但不对应物理文件,仅用于索引ConsumeQueue,MessageQueue对Broker是多对一的关系
- CommitLog:全局文件,不分Topic和queue,唯一的全局存储,写满后轮转
保证消息不丢失
RocketMQ的Broker是主从模式,可以用flushDiskType=SYNCFLUSH同步刷盘+brokerRole=SYNCMASTER保证Slave复制成功后才返回成功,但由于MessageQueue不像partition那样有副本托管在不同broker上,一旦承载这个queue的broker宕机,会导致queue不可用,需要人工/脚本提升slave的角色
局部有序
与Kafka相同,需要局部有序的消息送到同一个MessageQueue
负载均衡
RocketMQ的负载均衡以Topic为维度,在客户端实现,每个Consumer启动时有一个20s的定时任务周期执行均衡(去中心化的计算不保证强一致性,或是有以下条件时由Broker通知执行
- 消费者数量变化:新消费者/上线/下线
- 定时触发
- Topic增加Queue
- Broker路由信息更新,如已承载Queue的下线,新Broker承载Queue
- 订阅关系变化
流程:
- 消费者实例从Name server和Broker获取Topic下的MessageQueue以及所属消费者组的消费者列表
- 各消费者对Broker和MessageQueue排序,由于是各自独立计算所以每个consumer在开始计算前看到的元数据应一致(如果不一致的话20s后还会再均衡
- 队列名单一致性:所有 Broker 都会定时向所有 NameServer 上报自己的 Topic 路由信息。Consumer 在 Rebalance 之前,会从 NameServer 获取最新的路由。
- 消费者名单一致性:同一个 ConsumerGroup 下的所有消费者,都会定时向 该 Topic 所有的 Broker 发送心跳。Broker 会在内存中维护一份完整的
ConsumerID列表。
- 独立计算自己应该消费的Queue列表
- 停止删除队列的拉取任务,持久化offset并释放队列
常用策略:
- AllocateMessageQueueAveragely:与Kafka的RangeAssignor相同
- AveragelyByCircle:与Kafka的RoundRobinAssignor相同?
- MachineRoomNearby:先使用AllocateMessageQueueByMachineRoom只返回与本消费者同机房的Queue,如果有消费者没有任何Queue则放弃主策略采用fallback策略全部重新分配
总结?
一些核心的共同点:
- Topic作为逻辑单位组织消息
- 具备一个严格有序的结构(partition/MessageQueue)作为Topic的并发单元
- 由ConsumerGroup作为业务单元订阅Topic
- ConsumerGroup中单个消费者与并发单元对接实现并发与局部有序
- 写入消息并保证冗余之后向返回成功写入避免消息丢失