架构师对 MQ 的理解

127 阅读10分钟

image.png

消息中间件应用场景

  • 异步处理
    • 用户注册后,需要发送邮件或短信。
  • 应用解藕
    • 用户下单后,需要通知库存系统。
  • 流量削峰
    • 秒杀场景,导致流量暴增。
  • 消息通讯
    • 点对点通讯
  • 海量数据同步(记录日志)
    • 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.关闭信道,关闭连接 image.png

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链接到已存在的客户端。

image.png

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,并重建索引
  • 数据可靠性
    • acks参数设置可靠性级别 (request.required.acks: -1)
      • 0:不论写入是否成功
      • 1:Leader 写入成功后返回Response
      • -1:所有ISR列表写入成功后返回Response
  • 数据一致性
    • 某条消息对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

image.png