消息中间件

173 阅读11分钟

一.MQ

image.png

Rabbit MQ:安全

二.kafka

日志收集、消息系统、活动追踪、运营指标、流式处理、时间源等。

  • 建立实时流数据管道,以可靠地在系统或应用程序之间获取数据
  • 构建实时流应用程序,以转换或响应数据流

1.架构

1.1Messages And Batches

Kafka 的基本数据单元被称为 message(消息),为减少网络开销,提高效率,多个消息会被放入同一批次 (Batch) 中后再写入。

消息可以有一个可选的元数据, 也就是键(key)。键也是一个字节数组,与消息一样,对于 Kafka来说也没有特殊的含义。 当消息以一种可控的方式写入不同的分区时,会用到键。最简单的例子就是为键生成一个一致 性散列值,然后使用散列值对主题分区数进行取模,为消息选取分区 。这样可 以保证具有 相同键的消息总是被写到相同的分区上

偏移量是另一种元数据,它是一个不断递增的整数值,在创建消息 时, Kafka 会把它添加到消息里。在给定的分区里,每个消息的偏移量都是唯 一 的。消费者把每个分区最后读取的消息偏移量保存在 Zookeeper或 Kafka上,如果消费者关闭或重启,它的读取状态不会丢失。

1.2Topic

不同topic的数据分开存储,类似数据库的表

1.3Partition

topic中的数据分割为一个或多个partition,每个topic至少有一个partition,每个partition中的数据使用多个segment文件存储。
一个Partition物理上对应一个文件夹segment对应文件,记录只会append到segment中,清除过期日志时,直接删除对应的segment,segment文件由.index文件和.log文件组成,index存储数据和对应的偏移量的关系,log存储数据
单个partition中数据是有序的,如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1

1.3.1Leader

每个partition有多个副本,其中有且仅有一个作为Leader,Leader是当前负责数据的读写的partition。

1.3.2Follower

Follower跟随Leader,所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。
如果Leader失效,则从Follower中选举出一个新的Leader。
当Follower与Leader挂掉、卡住或者同步太慢,leader会把这个follower从“in sync replicas”(ISR)列表中删除,重新创建一个Follower。

ISR (In-Sync Replica)
Leader维护一个与其基本保持同步的Replica列表, 每个Partition都会有一个ISR, 而且是由leader动态维护. 如果一个flower比一个leader落后太多, 或者超过一定时间未发起数据复制请求, 则leader将其重ISR中移除. 当ISR中所有Replica都向Leader发送ACK时, leader才commit Leader 宕机之后, 会从 ISR 选择数据最新的 Follower 来当做 Leader 如果 ISR 全部宕机, 则选择第一个回复的 Replica 当做 Leader 节点 (消息可能会丢失或者重复消费)

两个条件判断,一个是同步时间,默认是10s,一个是相差条数,在0.9版本之前,两个条件都使用,所以会存在如果发送一个批次的数据量较大时大,所有的follower都与leader相差过大,而被移出列表,在较短时间后,数据写入完成,又被加回列表.导致不必要的频繁操作.0.9版本后移除了条数的判断条件

1.4Broker

kafka集群的服务器节点称为broker,broker存储topic的数据
单个 broker可以轻松处理数 千个分区以及每秒百万级的消息量。
一般broker的数量要大于topic的分区partition数量,每个broker存储一个partition的数据

1.5Producers

发送消息可以指定key,相同key会发送到同一个分区(hash)
参数acks配置:

  • 0 producers不等待broker返回ack)
  • 1 producers等待Partition的leader写入数据成功返回ack)
  • -1(all) :等待leader和Isr的follower都同步完成返回ack,如果同步数据成功没有返回ack时,leader故障,数据重发,可能造成数据重复 )
    Sync Producer:消息发送成功才会发送下一条,低延迟,低吞吐,不会丢失 Aync Producer:消息存放到QUEUE,后台异步批量发送

1.6Consumers

Consumer 消费的是Partition 的数据 在 Kafka 中, 一个 Topic 是可以被一个消费组消费, 一个Topic 分发给 Consumer Group 中的Consumer 进行消费, 保证同一条 Message 不会被不同的 Consumer 消费 注意: 当Consumer Group的 Consumer 数量大于 Partition 的数量时, 超过 Partition 的数量将会拿不到消息

Consumer采用pull的方式消费数据 分区分配策略:...TODO

1.7Replica

副本,小于等于broker数量,每个分区的副本可用brokerid表示,所有分区的副本默认情况下均匀分布在所有broker上

Kafka broker默认的消息保留策略 是这样的:要么保留一段时间(比如 7天),要么保留到消息达到一定大小的字节数(比 如 1GB)。当消息数量达到这些上限时,旧消息就会过期井被删除,所以在任何时刻, 可 用消息的总量都不会超过配置参数所指定的大小。

image.png

2.分区再均衡Rebalance

分区的所有权从一个消费者节点转移到另一个消费者节点,这样的行为被称为再均衡。

再均衡为Kafka的消息消费带来了高可用性和伸缩性,从而让我们可以放心地添加或者移除消费者,也能很好地处理单节点故障。但是在再均衡期间,消费者无法读取消息,会造成整个群组的一小段时间不可用。另外,当分区被重新分配给另一个消费者时,消费者当前的读取状态会丢失,它可能还需要去刷新缓存,在它恢复状态之前会拖慢整个应用程序。因此,在正常情况下我们并不希望发生再均衡行为。

消费者通过向被指派为群组协调器broker发送心跳来维持它们和群组的从属关系以及它们对分区的所有权关系,消费者会在轮询(为了获取消息)或者提交偏移量的时候发送心跳,来证明自己是活跃的,如果消费者停止发送心跳的时间过长,那么会话就会过期,群组协调器认为它已经死亡,就会触发一次再均衡。

当消费者要加入群组时,它会向群组协调器发送 JoinGroup 请求。第一个加入群组的消费者将成为“群主”。群主从协调器那里获得群组的成员列表(列表中包含了所有最近发送过心跳的消费者,它们被认为是活跃的), 并负责给每一个消费者分配分区。它使用一个实现了 PartitionAssignor 接口的类来决定哪些分区应该被分配给哪个消费者。

缺点:Kafka 中文件的布局是以 Topic/partition ,每一个分区一个物理文件夹,在分区文件级别实现文件顺序写,如果一个 Kafka 集群中拥有成百上千个主题,每一个主题拥有上百个分区,消息在高并发写入时,其 IO 操作就会显得零散,其操作相当于随机 IO,即 Kafka 在消息写入时的 IO 性能会随着 topic 、分区数量的增长,其写入性能会先上升,然后下降

还有从功能性上来考虑,例如 RocketMQ 提供了丰富的消息检索功能、事务消息、消息消费重试、定时消息等。从业务的角度考虑,通常在大数据、流式处理场景基本选用 Kafka,业务处理相关选择 RocketMQ。

Kafka 为什么速度那么快?

  • 顺序写入
  • 分页存储
  • Memory Mapped Files(内存映射文件)
  • 基于sendfile实现Zero Copy
  • 如果每个消息都压缩,但是压缩率相对很低,所以Kafka使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩Kafka允许使用递归的消息集合,批量的消息可以通过压缩的形式传输并且在日志中也可以保持压缩格式,直到被消费者解压缩Kafka支持多种压缩协议,包括Gzip和Snappy压缩协议

如何解决Kafka数据丢失问题?

1.设置 retries 为一个较大的值。这里的 retries 同样是 Producer 的参数,对应前面提到的 Producer 自动重试。当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了 retries > 0 的 Producer 能够自动重试消息发送,避免消息丢失。 2.设置 acks = all。acks 是 Producer 的一个参数,代表了你对“已提交”消息的定义。如果设置成 all,则表明所有副本 Broker 都要接收到消息,该消息才算是“已提交”。这是最高等级的“已提交”定义。 3.Producer要使用带有回调通知的API,也就是说不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。 4.Consumer 禁用自动提交:enable.auto.commit=false

配置auto.offset.reset 这个参数指定了在没有偏移量可提交时(比如消费者第l次启动时)或者请求的偏移量在broker上不存在时(比如数据被删了),消费者会做些什么。 这个参数有两种配置。一种是earliest:消费者会从分区的开始位置读取数据,不管偏移量是否有效,这样会导致消费者读取大量的重复数据,但可以保证最少的数据丢失。一种是latest(默认),如果选择了这种配置, 消费者会从分区的末尾开始读取数据,这样可以减少重复处理消息,但很有可能会错过一些消息。

如何确定合适的Kafka主题的分区数量?

一个简单的计算公式为:分区数 = max(生产者数量,消费者数量)

生产者数量=整体生产吞吐量/每个生产者对单个分区的最大生产吞吐量 消费者数量=整体消费吞吐量/每个消费者从单个分区消费的最大吞吐量

Kafka如何解决数据不一致问题?

场景:leader有10条数据,一个follower1同步了9条,一个follower2同步了8条,此时leader故障,follower2短暂离开ISR,follower1选为leader,此时follower2回到ISR,或者leader恢复,导致主从数据不一致

解决: LEO:ISR每个副本最后一个offset,log end offset HW:所有副本中最下的LEO high watermark HW之前的数据才对消费者可见,保证消费的数据一致性 如果是某个follower故障恢复,它会从本地磁盘读取之前记录的HW,将log截取掉大于HW的部分,重新从leade进行同步. 如果是leader故障,在选举新的leader后,其他所有follower都将log截取掉大于HW的部分,重新从leader进行同步.

可以保证数据一致,配合ack保证数据不丢失.

三.RocketMq

image.png

image.png

  • 第一、 Broker 做了集群并且还进行了主从部署,由于消息分布在各个 Broker 上,一旦某个 Broker 宕机,则该 Broker 上的消息读写都会受到影响。所以 RocketMQ 提供了 master/slave 的结构,salve 定时从 master 同步数据(同步刷盘或者异步刷盘),如果 master 宕机,则 slave 提供消费服务,但是不能写入消息。
  • 第二、为了保证 HA,我们的 NameServer 也做了集群部署,但是请注意它是去中心化的。也就意味着它没有主节点,你可以很明显地看出 NameServer 的所有节点是没有进行 Info Replicate 的,在 RocketMQ 中是通过单个 Broker 和所有 NameServer 保持长连接,并且在每隔 30 秒 Broker 会向所有 Nameserver 发送心跳,心跳包含了自身的 Topic 配置信息,这个步骤就对应这上面的 Routing Info。
  • 第三、在生产者需要向 Broker 发送消息的时候,需要先从 NameServer 获取关于 Broker 的路由信息,然后通过轮询的方法去向每个队列中生产数据以达到负载均衡的效果。
  • 第四、消费者通过 NameServer 获取所有 Broker 的路由信息后,向 Broker 发送 Pull 请求来获取消息数据。Consumer 可以以两种模式启动—— 广播(Broadcast)和集群(Cluster)。广播模式下,一条消息会发送给同一个消费组中的所有消费者,集群模式下消息只会发送给一个消费者。