常见消息传递模式
消费模型
- 推送模型
由消息代理记录消息的消费状态
- 拉取模型(Kafka)
由消费者记录消息的消费状态
通信模式
- 点对点消息传递模式
一条消息只能消费一次
保证数据处理的顺序性
- 发布订阅消息传递模式
消费者可以订阅一个或多个topic
消费者可以消费该topic中所有的数据
同一条数据可以被多个消费者消费
数据被消费后不会立马删除
Kafka
分区模型
- 每个topic可以有多个分区
- 一个分区只能被一个消费者组的一个消费者消费
- 分区内保证消息有序
分布式模型
- 每个topic的多个分区日志分布式的存储在Kafka集群上
- 每个topic
历史数据删除机制
而Kafka集群会保留所有的消息,无论其被消费与否
- 基于时间
- 基于partition大小
生产者
三种ACK机制
- producer不等待broker同步完成的确认,继续发送下一条(批)信息,leader死亡便会丢失数据
- producer要等待leader成功收到数据并得到确认,才发送下一条message,leader死亡,follower未来得及复制消息便会丢失数据
- producer得到follwer确认,才发送下一条数据,不会丢失数据,持久性好,延时差
支持同步发送消息和异步发送消息
- 同步:返回Future时调用get方法,没有返回结果前会一直阻塞
- 异步:提供一个回调函数,send后可以继续发送消息,不用等待返回结果,有返回结果时自动调用回调函数
选择消息分区
- 负载均衡
- 没键值:自增轮询
- 有键值:散列后取模
记录收集器(RecordAccumulator)
- 在客户端缓存消息,等待时机批量发送
- 将待发送的消息按照分区存储
- 每个分区有一个双端队列缓存批记录
- 批记录是按顺序存储的特定分区的一批消息
消息发送线程(send)
- 针对每个节点都创建一个客户端请求
- 发送方式
- 按照分区直接发送
- 按照分区的目标节点发送,按照分区的目标节点收集RecordBatch并按照节点发送消息 // 一般此种方式网络开销少一些
将消息发送到
客户端网络连接对象(NetworkClient)
- 管理了客户端和服务端之间的网络通信,包括连接的建立、发送客户端请求、读取客户端响应
- NetworkCLient的底层网络操作由选择器(Selector)完成
- ready()方法:从记录收集器获取准备完毕的节点,并连接所有准备完毕的节点
- send()方法:为每个节点创建一个客户端请求,将请求暂存到节点对应的通道中
- poll()方法:轮询,真正执行网络请求,发送请求给节点,并读取响应
客户端轮询并调用回调函数
- 发送线程创建的客户端请求对象包含请求本身和回调对象
选择器(Selector)
- 以轮模式驱动不同事件
Kafka网络通道(KafkaChannel)
- 负责请求的发送和响应接收
消费者
- 一个分区只能被消费组的一个消费者消费
- 消费组再平衡,一旦有消费者加入或推出消费者组则会执行次机制
- 消费者保存分区消费进度到zookeeper,因为拉取模型
- 同一个分区内消息有序
- 消费组的信息保存再zookeeper内
消费者与ZK的关系
- ZK存储了Kafka内部元数据、消费者成员列表、分区的消费进度、分区的所有者
分区分配给消费者
- 线程数量多于分区数量,有部分线程无法消费该主题下的任何一消息
- 线程数量少于分区数量,有一些线程消费多个分区的数据 // 此种方案较为优异
- 线程数量等于分区数量,正好一个线程消费一个分区的数据
消费者提交偏移量
- 自动提交
每隔一段时间提交一次偏移量,消费者组再平衡的时候会造成重复消费(未能提交偏移量),每次提交都需要和zookeeper通信一次
- 手动提交
Broker
分区有leader节点和follower节点
每次生产者只将数据写到leader节点,follower节点会自动拉取leader节点的数据
没有内存压力,十分适合采用pull模型
常见问题
Kafka为什么快
- 采用了OS的Page cache技术:通过将磁盘中的数据缓存到内存中
- Batch缓冲池,避免频繁GC
- 批量发送,记录收集器,按照分区目标节点发送,减少网络I/O的次数
- 生产者顺序I/O
- 采用partition机制并行处理
- 支持数据压缩,减少网络I/O的消耗
- 采用零拷贝技术:数据直接从内核空间拷贝到网卡内存,不经过用户空间了
判断Kafka节点活着
- 必须维持与zk的会话,采用ZK的心跳检测
- 及时同步leader节点的数据,延迟不能太久
数据丢失原因
- producer,见producer ACK机制
- consumer,在自动提交offset的时候,数据消费正好发生异常
保证可靠性
- ack机制:ack=1,消息写入leader即成果,ack=0,消息发送即成功,ack=all
- 手动提交消费偏移量
- 消息发送失败重试
RabbitMQ
- Erlang开发
- 基于AMQP协议实现
优势
- 可靠性(Reliablity):使用了一些机制来保证可靠性,比如持久化、传输确认、发布确认。
- 灵活的路由(Flexible Routing):在消息进入队列之前,通过Exchange来路由消息。对于典型的路由功能,Rabbit已经提供了一些内置的Exchange来实现。针对更复杂的路由功能,可以将多个Exchange绑定在一起,也通过插件机制实现自己的Exchange。
- 消息集群(Clustering):多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
- 高可用(Highly Avaliable Queues):队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
- 多种协议(Multi-protocol):支持多种消息队列协议,如STOMP、MQTT等。
- 跟踪机制(Tracing):如果消息异常,RabbitMQ提供了消息的跟踪机制,使用者可以找出发生了什么。
整体架构
RabbitMQ各组件功能
- Broker:标识消息队列服务器实体。
- Virtual Host:虚拟主机,用于逻辑隔离。标识一批交换机、消息队列和相关对象。
- Exchange:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
- Queue:消息队列,用来保存消息直到发送给消费者,一个消息可投入一个或多个队列
- Banding:绑定,用于消息队列和交换机之间的关联。
- Channel:信道,多路复用连接中的一条独立的双向数据流通道。
- Connection:网络连接,比如一个TCP连接。
RabbitMQ的多种Exchange类型
RabbitMQ常用的交换器类型有direct、topic、fanout、headers四种。
Direct Exchange
Direct交换器将所有发送到该交换器的消息被转发到RoutingKey指定的队列中,也就是说路由到BindingKey和RoutingKey完全匹配的队列中。
Topic Exchange
Topic交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键(routing-key)和绑定键(bingding-key)的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:"#"和"*"。#匹配0个或多个单词,匹配不多不少一个单词。
Fanout Exchange
该类型不处理路由键,会把所有发送到交换器的消息路由到所有绑定的队列中。优点是转发消息最快,性能最好。
Headers Exchange
该类型的交换器不依赖路由规则来路由消息,而是根据消息内容中的headers属性进行匹配。headers类型交换器性能差,在实际中并不常用。
TTL
TTL(Time To Live):生存时间。RabbitMQ支持消息的过期时间,一共两种。
在消息发送时可以进行指定。通过配置消息体的properties,可以指定当前消息的过期时间。
在创建Exchange时可进行指定。从进入消息队列开始计算,只要超过了队列的超时时间配置,那么消息会自动清除。
死信队列DLX
- 消息被拒绝
- 消息TTL过期
- 消息队列达到最大长度
可靠性保证
消费者
- 将autoAck设置为false,手动确认
生产者
- 提供事务机制,发送失败则事务回滚
- 发送方确认机制
Broker
- 消息持久化