【消息队列】【消息中间件】JMS,RocketMQ,Kafka

前言 

消息中间件首先我们会有三种作用:异步、解耦、削峰;

异步:进程内事件也可以异步,但是无法做分布式系统的异步。

解耦:相对于观察者模式,消息中间件还拥有发布订阅的功能。

削峰:中间件特有,利用中间特效,可以堆积大部分事件。 

特性

消息中间件一般要解决的问题:

  • 发布订阅
  • 优先级消息队列
    • 如果是在内存中的队列,可以用堆做数据结构;
    • 因为消息中间件基本要存放磁盘,所以难以实现;
    • 消息中间件可以通过设置多种不同的队列,然后把不同的优先级放到不同的队列中;
  • 消息顺序消费
    • 消息完全按顺序可以单队列,单消费者
    • 消息按照事务有顺序,可以支持多队列实现,用事务id做哈希,同一个事务的在一个队列
  • 消息的过滤
    • broker端过滤,加大服务器负担,但是可以减少网络传输
    • consumer端过滤,无用的网络传输
  • 消息持久化
    • 数据库、KV系统、文件
    • 文件可以利用零拷贝,page cache提高性能
  • 消息高可用
  • 消息低延迟
    • pull模型,push模型,长轮训
  • 至少消费一次
  • 回溯消息
  • 堆积消息
    • 注意有些情况下加大消费者不一定能解决堆积问题,因为很多时间消息者和队列是1:1,所以队列的数量是关键,所以这方面RocketMQ是有优势
  • 分布式事务
    • 如何保证事务,分布式一般可用几种都可以考虑,XA,TCC等
    • RocketMQ采用offset方式
  • 定时消息
    • 优先级做定时消息,磁盘难以实现
    • 可以按照精度,提供5s,10s等等在不同队列,FIFO实现
  • 消息重试
    • 失败原因是消息,那么可以让消息10s,20s,5min等间隔重试
    • 失败原因是下游系统,那么可以让topic sleep一段时间,减轻broker压力

高可用性

  镜像集群模式:其实这种不是真正的分布式,每台机器都需要存全量的数据,而且必须要做同步双写/多写,十分影响性能

  分布式Partition模式:类似Kafka:

  • 就是一个Topic可以分成多个Partition,不同Partition存不同数据可以存在不同的Broker上面
  • 每个Partition都有他们的Replica副本,也存在在不同的Broker上面
  • 每个而且他们之中会选举一个Leader,多个Follower出来。

  Master/Slave模式:差不多同镜像集群模式,但是可以多个Master,从而也是分布式的

零拷贝

参考我的文章:https://www.cnblogs.com/iCanhua/p/14674602.html

主要是介绍mmap和sendfile两种零拷贝:

  • 用户态和内核态切换的成本,他们是不同进程、线程处理的,所以有调度成本
  • 数据多次在用户态和内核态copy的成本。零拷贝可以做到减少拷贝
  • mmap比较适合小块文件传输,所以适合consumer一个个消息发出去过程
  • sendfile很好的利用DMA技术,所以大块文件比较适合

Kafka

  同样由多个 broker 组成,每个 broker 是一个节点;

  分布式:创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据。  这就是天然的分布式消息队列,就是说一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。

  逻辑结构图片

  高可用:Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。

  高性能:同样是零拷贝,因为kafka是每个topic单独一个队列,所以如果很多topic的场景,就会变成随机写,性能会下降,所以kafka适合topic少的场景

  问题:Kafka在一个broker下无法支持过多的partition,因为当并发量大的时候,过多的partition就会导致磁盘变成随机写,会有性能问题。

  特点:适合流式数据,因为并发大,读也快。

  • 针对写入,只需写一个partition队列即可,顺序写可达秒级百万,所以写入数据特别快
  • 单机支持的队列数量不能太多,因为topic和partition的机制,队列数量=topic数量*partition数量,所以单机支持不了多少topic。不太适合业务使用
  • 消费速率也非常快。

RocketMQ

  角色:Producer,Consumer,NameService,Broker

  高可用:Broker的高可用主要是依靠把Broker分为Master和Slave,当然可以多个Master,这点和Kafka不同。

  元数据及配置:NameServer无状态,主要作用是维护Broker的路由信息,并和Producer、Consumer使用长链接同步。

  Consumer的信息存储在Master上面,包括offset,所以NameService无状态,而且消息堆积能力可以实现。

  Broker逻辑结构图片

  队列设计:Rocket的设计和kafka不同,他用一个实际存数据的队列commit log,另外一个是逻辑队列用作对外表达。可以理解commite log是数据库(kafka没有这个库),而consummer queue是对应kafka的partition。目的是为了做到队列的轻量化。

  • 所有的数据都放在一个commit log完全顺序写,随机读
  • 队列实际只存储消息在CommitLog的位置信息,并且串行方式刷盘。

  以上设计特点是:

  • 队列轻量化,单个队列的数量量非常小。
  • 磁盘访问并行化,减少磁盘竞争冲突,但是读会变成随机读。
    • 但这个问题不会影响很大,因为随机读其实是跳跃读,局部性原理。
    • 最好机器内存大一些,可以让pageCache也大一些,增加缓存命中率
  • 先读consumerQueue再读commitLog开销大
    • 由于ConsumeQueue存储数据量极少,而且是顺序读,在PAGECACHE预读作用下,ConsumeQueue的读性能几乎与内存一致,即使堆积情况下。所以可认为 Consume Queue 完全不会阻碍读性能。
    • Commit Log 中存储了所有的元信息,包含消息体,类似于 Mysql、Oracle 的 redolog,所以只要有 CommitLog 在,Consume Queue 即使数据丢失,仍然可以恢复出来。

  消息查询:既然做了commit log作为完整消息的数据存储,那么必然有一个根据轻量队列中的内容反查完整的数据

  • 根据messageId查询:MsgId 总共 16 字节,包含消息存储broker地址,消息 Commit Log offset。从 MsgId 中解析出 Broker 的地址和Commit Log 的偏移地址,然后按照存储格式所在位置消息 buffer 解析成一个完整的消息。
  • 根据messageKey查询,这里用了一个slot table作为messageKey的索引表,先用key做hash到具体的slot table,取其中的值获取commit log的位置。

  消息速率:如上图所示,一个Topic对应多个分区队列(consumerQueue),实际消费过程中,基本是一个消费者一个分配一个分区队列。所以当分区数量和消费者数量一致的时候,增加消费者无法提高消费速率。

  ProcessQueue:消费者属于Pull模式,可以自己控制消费速率,每一次Pull数据都会放在JVM中的ProcessQueue中,当ProcessQueue大小达到一定门槛(可配置)后,不会再去拉取消息。

  高性能:高性能常见主要针对多topic场景,所以适合集团中间件

  • 写文件是顺序写,因为都写commit log所以就算很多topic都不会有性能问题
  • Consumer消费读取使用零拷贝,因为消费是属于小块数据传输的要求,所以用mmap比sendfile的效果要好

  定时消息:不支持任意定义定时消息,只支持5s,10s,30s类似的定时。

  • 不支持的原因是定时消息一般要排序,磁盘无法排序
  • 实现原理简单,只需要分别为他们做一个队列,尾插法,然后定时每秒扫描一下就好了。

  事务消息:事务消息使用的是二阶段提交。

  

  负载均衡

  • 发送者负载均衡,是按照轮训方式,对不同的分区进行发送消息
  • 消费者负责均衡,一个分区只能对应一个消费者,所以增加消费者不能大于队列数量,因为大于也无法获取分区进行消息消费

  部署模式

  • 单台Master
  • 多台Master
    • 性能最高,单台宕机不影响可用性,但影响部分消息实时性
  • 多台Master多台Slave异步复制
    • 异步复制采用的是Slave拉取Master的CommitLog
    • 类似MySql主从同步
  • 多台Master多台Slave同步双写
    • 性能会比异步复制低一些
    • 可以保证不会数据丢失,可用性高

  消息堆积:消息堆积会不会对生产和消费性能有影响呢?这得看情况,但首先要明确,一般消息是不会堆积的,所以在内存就会被消费掉,然后慢慢落盘。

  • 有slave情况下,不会有影响,因为master发现consumer在消费磁盘的数据的情况下,就会下达指令给consumer让它重路由到slave中去读取消息。
  • 无slave情况下,会瘦影响

  特点:适合业务使用。

  • Rocket的topic数据集中都写在一个队列中,而不依靠parition进行高可用,因为队列轻量化,所以单机可以比kafka支持更多的队列。支持单机上万队列
  • 多种分布式实现:同步/异步刷盘;主从同步/异步复制

刷盘策略:

  同步刷盘:用户线程先写到PageCache中,然后等待内核线程刷盘,再唤醒用户线程返回

  异步刷盘:用户线程写到PageCahce后立马返回,内核异步刷盘

消息丢失:

 消息的丢失可能发生在三个阶段:

  • 生产者阶段:从消息被生产出来,然后提交给 MQ 的过程中,只要能正常收到 MQ Broker 的 ack 确认响应,就表示发送成功,所以只要处理好返回值和异常,这个阶段是不会出现消息丢失的。
  • 消息存储阶段:这个阶段一般会直接交给 MQ 消息中间件来保证,所以要了解它的原理,比如 Broker 会做副本,保证一条消息至少同步两个节点再返回ack。
  • 消费者阶段:消费端从 Broker 上拉取消息,只要消费端在收到消息后,不立即发送消费确认给 Broker,而是等到执行完业务逻辑后,再发送消费确认,也能保证消息的不丢失。

  消息跟踪:每条消息都应该有一个唯一的ID,我们生产者在生产消息,消费者在消费消息的情况下,都应该能做到一一的实例跟踪,这样才能做到排除和幂等的跟踪、监控。这就是消息的唯一性,也是做消息防止丢失、重发的基础。

  • 在分布式系统下,这种消息ID和验证机制一般由中间件构件完成,所以需要分布式ID生成方案。
  • 消息ID还可以具有索引功能

  消息幂等:为了做消息的高可用和丢失机制,很可能会产生消息重复投递的情况,所以要做消息的幂等功能。

  • 方案1时通过改造业务代码进行消息的幂等。
  • 方案2时通过消息的ID进行消息的幂等,保证一个ID只消费一次,但这个不可靠。因为无法避免生发送不幂等

 

posted @ 2019-05-07 13:37  饭小胖  阅读(574)  评论(0编辑  收藏  举报