消息中间件应用场景
- 异步处理
- 用户注册后,需要发送邮件或短信。
- 应用解藕
- 用户下单后,需要通知库存系统。
- 流量削峰
- 秒杀场景,导致流量暴增。
- 消息通讯
- 点对点通讯
- 海量数据同步(记录日志)
- binlog日志同步
- 前端做埋点日志发送到kafka,在存入es中
- 解决分布式事务
- 确保生产者存入MQ服务器中
- MQ消费者正常消费消息,手动ack(注意幂等)
- 采用补单机制
- 订单创建后,有回滚,此时需要补偿
- 补单队列和派单队列需要绑定到同一个路由键中
消息可靠性
- 发送可靠性:确保消息成功发送到Broker
- 最少一次:开启confirm机制,确保消息传输到MQ服务器中
- mandatory参数确保保存到MQ队列中,不会被丢弃
- 最多一次,不能保证消息会发送成功
- 存储可靠性:Broker对消息持久化,确保消息不会发送丢失
- 消息可靠性:确保消息成功被消费
- 消息消费时,需要autoAck设置为false,然后通过手动确认方式区确认已经正确消费消息,以免消费端引起不必要的消息丢失
RabbitMQ
- Producer:生产者,创建消息,然后发布到RabbitMQ中
- Broker:服务节点
- Virtual Host:虚拟主机,拥有自己的队列,交换器,绑定和权限机制
- channel:频道,建立在connection连接之上的一种轻量级连接
- RoutingKey:路由键,生产者将消费发给交换器的时候,指定一个路由键,用来制定路由规则
- Exchange:交换器,生产者将消息发送到Exchange由交换器将消息路由到一个活多个队列中,如果路由不到,返回给生产者或丢弃。
- fanout扇形交换器:所有与该交换器绑定的队列中
- direct直连交换器:Bindingkey和Routingkey完全匹配的队列
- topic主题交换器:与direct列,通过通配符进行模糊匹配
- headers头交换器:不依赖于路由键的匹配规则来路由消息,而是消息内容中的headers属性进行匹配,性能差,不实用
- Queue:队列,RabbitMQ内部对象,用户存储信息
- Binding:绑定,RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键,这样RabbitMQ就知道如何正确将消息路由到队列中。
- Consumer:消费者,消费消息的一方,订阅在队列上
RabbitMQ 运转流程
- 生产者
- 1.生产者连接到 RabbitMQ Broker,建立一个连接,开启一个信道
- 2.生产者声明一个交换器,并设置相关属性(交换机类型,是否持久化)
- 3.生产者声明一个队列并设置相关属性(是否排他,持久化,自动删除)
- 4.生产者通过路由键将交换器和队列绑定起来
- 5.生产者发送消息到Broker中
- 6.相应的交换器根据接收到的路由键查找相匹配的队列
- 7.如果找到,则将消息存入相应队列中
- 8.如果找不到,则根据配置属性选择丢弃还是退回
- 9.关闭信道,关闭连接
- 消费者
- 1.生产者发送到Broker,建立一个连接,开启一个信道
- 2.消费者向Broker请求消费相应队列中的信息,并设置相应回调函数等
- 3.等待Broker回应并投递相应队列中的消息,并推送给消费者消息
- 4.消费者确认收到的消息
- 5.Broker从队列中删除相应已经被确认的消息
- 6.关闭信道,关闭连接
RabbitMQ 死信队列
死信队列是另一个exchange,然后可以重新消费,说白了就是没有被消费的消息换个地方等待重新被消费。 死信队列的形成原因
- 消息被拒绝
- 消息TTL过期
- 队列达到最大长度
Kafka
特性
- Kafka作为一个集群运行都在一个或多一个服务上,通过Topic对存储的流数据进行分类,每条纪录包含一个key,value,timestamp
- 高稳定性:O(1)的磁盘数据结构,这种结构对于TB级别的消息存储也能保持长时间的稳定性能
- 高吞吐量:TPS 10万
- 支持同步和异步复制两种HA模式
- 消费者主动拉,随机读,零拷贝,批量拉数据,数据压缩,多个副本机制提高并发处理能力
- 数据迁移,扩容都对用户透明,支持Hadoop并行数据价值,支持online和offline场景
- scal out:无需停机可拓展机器
- 定期删除机制:支持设置partitions的segment file保留时间
相关概念
- Topic(主题):数据记录的地方,可以用来区分业务系统。一个topic可以拥有一个或多个消费者来订阅它。
- Partition(分区):每个分区都是顺序的,不可变的消息队列,并可以持续添加。分区中的消息都被分为一个序号,称为偏移量,每个分区中偏移量都是唯一的。
- Segement:一个partition由多个segment组成
- Distribution(分布分散):Log的分区被分布到集群中的多个服务器上,每个服务器处理它分到的分区,根据配置每个分区还可以复制到其他服务器作为备份容错。
- Producers(生产者):往某个topic发布消息,生产者也负责选择发布到Topic上的那一个分区。默认是分区列表轮询策略,开发者也可以根据其他策略算法选择分区。
- Consumers(消费者):使用一个消费者组来进行标识,发布到topic中的每条纪录被分配到订阅消费组中的一个消费者实例。消费者实例可以分布在多个进程中或多个机器上。
- Consumer Group(消费者组):真实消费topic的标识
- replication(副本):每个partition会被复制到其他服务器作为一个副本,这是一种冗余备份策略,分区和副本分布在不同服务器上,区别是leader和follower,leader处理读写,follower被动复制数据提供读请求
- 四个核心API
- Producer API:发布一串流式数据到一个或多个topic中
- Consumer API:订阅一个或多个topic,并处理推送过来的流式数据
- Streams API:流处理器,消费topic产生的输入流,然后生产一个输出流到topic中,进行有效的转换
- Connector API:构建并运行可重用的生产者或消费者,将topic链接到已存在的客户端。
Kafka 拓展
拦截器
producer 允许用户指定多个interceptor 按序作用于同一条消息从而形成一个拦截链。
- 接口:ProducerInterceptor
- configure:获取配置信息和初始化数据时调用
- onSend:封装进kafkaProducer.send方法中,运行在用户主线程中。序列化及计算分区前调用
- onAcknowledgement:消息被应答或消息发送失败时调用,并在producer回调触发之前。
- close:关闭,用于执行清理工作
自定义分区
调用Producer API时,如果没有指定分区器,那么数据会使用默认分区器的算法分到各个分区。如果指定分区器,那么数据会使用指定的分区器的算法分到各个分区中。
- 接口:DefaultPartitioner
- 创建一个类(包名与接口一致)实现Partitioner接口,重写 partition接口
- 配置分区器:props.put("partitioner.class","com.kafka.MySampleParationer");
副本
- Topic 主题
- Offset 偏移量
- Partition 副本
- leader 主副本:处理所有读写请求
- follower 从副本:follower会被动定时复制leader上的数据
- 数据复制算法
- 如果leadr故障或宕机,新Leader被选举并接收客户端的消息成功写入
- 同步副本列表ISR选举一个follower追赶上的副本成为leader
- Leader负责维护和跟踪ISR,如果发现follower落后太多,会移除
- 什么原因导致分区副本与leader不同步
- 慢副本:一定周期时间内follower不能追赶上leader。
- 卡住副本:一定周期时间内follower停止leader拉取请求。
- 新启动的副本:当添加副本因子时,新的follower不再同步副本列表,直到他们完全赶上leader日志
- 恢复机制:
- broker fail时,重启后会读取RecoveryPoint 找到 segement,这些就是可能没有刷新到磁盘。
- 调用 segement的recover,重新读取各个segment的msg,并重建索引
- Partition 副本
- 数据可靠性
- acks参数设置可靠性级别 (request.required.acks: -1)
- 0:不论写入是否成功
- 1:Leader 写入成功后返回Response
- -1:所有ISR列表写入成功后返回Response
- acks参数设置可靠性级别 (request.required.acks: -1)
- 数据一致性
- 某条消息对consumer可见,即使leader宕机了,新的leader上数据依然可以被读到
- HighWatermark:高水位,一个partition对应ISR中最小的LEO作为HW
- 消费者最多只能消费到HW所在的位置
- 每个replica都有HW,leader和follower各自更新自己的HW
- HighWatermark <=leader.LogEndOffset
- leader新写入的msg,consumer不能立刻消费,leader会等待该消息被所有ISR中的replica同步后,更新HW,此时该消息才能被Consumer消费,即Consumer最多只能消费到HW
文件存储
- topic 存储分布
- 根目录:kafka/kafka-logs
- 副本分配逻辑规则
- kafka集群中,每个Broker都有均等分配partition的leader机会
- Partition-0:Broker1为leader(Leader不会分配在一个Broker中)
- Partition-1:Broker2为Leader(Leader不会分配在一个Broker中)
- Partition 存储分布
- 每个partition目录相当于一个文件被平均分配到多个大小相等的segment段数据中。每个segement file 消息数量不一定相等,这种特性方便old segment file 快速被删除。
- 每个partition只需要支持顺序读写就行了,segment文件生命周期由服务端配置参数决定
- 好处:可以快速删除无用文件,提高磁盘利用率
- segment 存储结构
- index file 和 data file:一一对应,索引文件和数据文件
- partition全局从第一个segment从0开始,后续每个segment文件为上一个segment文件最后一个消息的offset值。数值最大为64位long文件,19位数字字符长度,没有数字用0填充
- index file 索引文件
- data file 数据文件
- Message 物理结构
- 8 byte offset:偏移量表示partition的第多少message。
- 4 byte message size:message大小
- 4 byte CRC32:用crc32校验 message
- 1 byte magic:标识本次发布kafka服务程序协议版本号
- 1 byte attributes:表示为独立版本或标识压缩类型或编码类型
- 4 byte key length:表示key 的长度,当key为 -1 时,k byte key 字段不填
- k byte key:可选
- value bytes payload :表示实际消费数据
- 查看segment 日志
- bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files /data/kafka/kafka- logs/test-0/00000000000000000000.log --print-data-log
- offset 查找 message
- 查找segment file中,00000000000000000000.index 表示最开始的文件,起始偏移量为0。
- 00000000000000368769.index 的消息起始偏移量为 368769 + 1= 368770
- 比如当offset=368776时定位到 00000000000000368769.index|log