消息队列
消息队列(MQ),即一个保存消息的容器,其本质是一个队列。并且支持高吞吐,高并发,高可用。
Kafka
应用场景:常用于离线消息处理,如日志等。
Kafka使用:创建集群->新增Topic->编写生产者逻辑->编写消费者逻辑
基本概念
Topic:逻辑队列
Cluster:物理集群,每个集群可以建立多个不同的Topic
Partition:Topic的一个分片,不同分片可以并发处理
Producer:生产者,将业务消息发送到Topic中
Consumer:消费者
ConsumerGroup:消费者组,不同组的Consumer消息进度互不干涉
Offset:消息在partition内的相对位置信息,理解为唯一的ID,其在partition内严格递增,可以保证在同一个partition内部的消息具有顺序性。
Replica:每个分片中包含有多个Replica,Leader Replica从ISR中选出。Leader作用是接收Producer提供的消息供Consumer获取。Follower作用是保持与Leader的差距较小,在Leader宕机时被选为新的Leader继续工作。差距较大的Follower会被移出ISR。
Broker:是Kafka中的节点,多个Broker构成了Kafka集群。其中有一个担任controller角色,负责分配复制各个topic下的partition。
在Kafka集群之上通常有一个组件ZooKeeper,负责存储集群元信息,包括分区分配信息等。
消息的流程
Producer将多个消息通过压缩(ZSTD算法),降低IO消耗,减小消息大小发送到Broker。
Broker将消息副本以日志形式写入磁盘中。按照有序的日志段进行写入。往磁盘写入的时候是顺序写入,减少寻道时间,提高写入效率。
注:LogSegment写入时,文件名均以其第一个消息的Offset进行命名。e.g. 1.log,1.index,1.timeindex
Broker在接收到Consumer发来的FetchRequest请求后,将其指定的Offset处消息从磁盘中读取出来,按照时间窗口和消息大小窗口发个Consumer。
Broker从磁盘上寻到数据的方法是,根据得到的offset,通过二分查找文件名,找到小于目标offset的最大文件。进入偏移量索引文件后,因为其采用的是稀疏索引的方式,再次二分找到小于offset的最大索引位置,之后便通过顺序遍历的方式找到文件。对于时间戳索引,即在偏移量索引的基础上再加一级索引,由时间戳找到offset,再同上查找。
Broker零拷贝技术优化
在传统的Broker中,将数据从磁盘空间读入内核的Read Buffer后,进一步传递给用户态的应用程序缓存空间,再传递给内核的Socket Buffer,再传递给网卡NIC Buffer,最后再传递给消费者进程。
通过零拷贝技术后,在内核中Read Buffer直接将数据传递给了NIC Buffer,减少了三次拷贝复制。写入同理,甚至直接由用户态写入磁盘。
Partition在Consumer的分配
解决分配方式的两种方法:手动分配和自动分配
- 手动分配:程序编写时便确定Consumer获取哪个Partition。实现方便,运行较快,但缺点很多。
- 自动分配:在Kafka的Broker集群中对每一个
Consumer Group都选取一个Broker作为Coordinator,自动协调分配partition。
Rebalance流程:首先在一个Consumer Group中的Consumers会去按照一定算法去Broker集群中寻找Coordinator。统一确定了Coordinator后,Coordinator会在Consumer Group中确定一个leader,通常是第一个,它规定了一定程度的分配规则发送给Coordinator。Coordinator之后按照规则均衡分配partition给各Consumer。同时,每个Consumer会定期发送一个“心跳”给Coordinator,当Coordinator检测到Group中多出一个Consumer或者丢失了一个心跳后,重新进行Rebalance分配。
缺陷
- 在凡涉及节点的运维操作下,如升级重启,扩容缩容等,均会有大量的节点数据复制,此类操作需要很高的时间成本。
- 对于负载不均衡场景,解决方案复杂
- 没有自己的缓存,完全依赖page cache
- Controller和Coordinator和Broker在同一进程中,大量IO会降低性能
BMQ
兼容Kafka协议,存算分离,云原生消息队列。
BMQ的数据写入的底层是基于HDFS的写入实现的。
HDFS写文件:对Segment文件写入时,随机选择一定数量的DataNode进行写入。
数据的随机分配到一定数量的节点上,将所有节点打散分配到整个集群中,保证了数据的负载均衡。
存在Broker-Partition状态机,保证对于任意分片在同一时刻只能在一个Broker上存活。
recover:争夺写入权利,因为HDFS要求一个文件只能有一个进程写入;恢复真实数据。
写文件流程
先把数据写入到Buffer缓冲中,然后通过一个Writer Thread异步把buffer中的内容写入storage。此时有两种返回方法:
- 在写入buffer后返回已经写入成功,考虑高并发和吞吐
- 在写入storage后返回写入成功,考虑数据安全
save Checkpoint:告诉系统当前数据已经写入某个offset中了,已经可以查询了。
Failover机制:如果DataNode宕机不可写入,寻找一个新的可用节点,创建新的segment,继续写入
Proxy读取流程
获取到一个Fetch Request后进入到Wait流程中,Wait用于解决consumer过多的情况,降低IO压力。Wait流程后才是查找内容。
高级特性
泳道消息:解决主干泳道流量隔离问题以及泳道资源重复创建问题
- 在BOE测试时,实现多泳道并行处理。
- 在PPE测试时,把测试流量打到泳道上而不经过线上。
Databus:简化消息队列客户端复杂度,解耦业务与topic,缓解集群压力来提高吞吐。
- 直接使用原生SDK的客户端配置复杂,不支持动态配置,batch效果不佳
Mirror:通过最终一致性的方式,解决跨region读写问题
index:在BMQ中将数据结构化,配置索引DDL,异步创建索引,建立特定列名(如userID)和offset的映射关系,通过Index Query读出数据。
Parquet:Hadoop生态圈中的新型列式存储,兼容大部分计算框架。在BMQ中将数据结构化,通过Parquet Engine使用不同方式构建Parquet文件,便于大数据分析对某一列的获取。
RockMQ
用于低延时场景。
NameServer承担了一个路由的角色,告知生产者和消费者每个队列在哪个broker上,如何去进行生产和消费。