高级JAVA开发 MQ部分

MQ

参考和摘自:
中华石杉 《Java工程师面试突击第1季》
ActiveMQ—知识点整理
消息总线真的能保证幂等
ActiveMQ消息传送机制以及ACK机制详解

MQ的作用、为什么要用MQ

解耦、异步、消峰

应用场景:

  1. 解耦:利用 发布/订阅(Publish/Subscribe)模型,多服务订阅同一queue,省去生产者主动调用并维护多个消费者(service),消费者可随时unSubscribe,生产者并不感知。
  2. 异步:利用 点对点( Point-to-Point)模型,将多个任务分别放到不同队列中,之后直接返回。消费者各自从不同队列取得任务并消费。这么做的好处是不用阻塞等待多个任务全部返回再响应用户操作,加速响应。
  3. 消峰:利用 点对点( Point-to-Point)模型,在系统请求高峰期不采用阻塞式调用,将任务全部打入MQ中,让系统调用链中消费者慢慢消化任务。防止系统被访问高峰打死(很大原因是直接访问数据库,数据库成为瓶颈,后面也会在 缓存 章节继续分析)。

常见的MQ的优缺点

摘自中华石杉老师的笔记

特性 ActiveMQ RabbitMQ RocketMQ Kafka
单机吞吐量 万级
吞吐量比RocketMQ和Kafka要低了一个数量级
万级
吞吐量比RocketMQ和Kafka要低了一个数量级
10万级
RocketMQ也是可以支撑高吞吐的一种MQ
10万级
这是kafka最大的优点,就是吞吐量高。
一般配合大数据类的系统来进行实时数据计算、日志采集等场景
topic数量对吞吐量的影响 topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降。
这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic
topic从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源
时效性 ms级 微秒级
这是rabbitmq的一大特点,延迟是最低的
ms级 延迟在ms级以内
可用性
基于主从架构实现高可用性

基于主从架构实现高可用性
非常高
分布式架构
非常高
kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消息可靠性 有较低的概率丢失数据 经过参数优化配置,可以做到0丢失 经过参数优化配置,消息可以做到0丢失
功能支持 MQ领域的功能极其完备 基于erlang开发,所以并发能力很强,性能极其好,延时很低 MQ功能较为完善,还是分布式的,扩展性好 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准
优劣势总结 非常成熟,功能强大,在业内大量的公司以及项目中都有应用。
偶尔会有较低概率丢失消息。而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本。
而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用。
erlang语言开发,性能极其好,延时很低;
吞吐量到万级,MQ功能比较完备
而且开源提供的管理界面非常棒,用起来很好用
社区相对比较活跃,几乎每个月都发布几个版本分
在国内一些互联网公司近几年用rabbitmq也比较多一些
但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。
而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。
而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。
接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障
日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景
而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控
社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码
还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的
kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展
同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量
而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略
这个特性天然适合大数据实时计算以及日志收集

使用MQ带来的问题以及处理办法

MQ带来的问题列举

  1. 系统可用性降低,需要保证MQ健康的运行,否则MQ挂掉会导致整个应用不可用,后果难以设想。
  2. 系统的复杂性变高,比如消息重复消费、消息丢失 等等,需要用一些手段来处理,怎么处理接下来细说,这里将会成为重要考点。
  3. 一致性问题(分布式事务)。A、B、C、D四个任务必须同时成功,放入MQ后A、B、C都成功了,偏偏D任务失败了该如何处理。这也是重要考点。

消息重复消费(幂等)问题

消费者拿到消息但是还没来得及ACK(或者还没来得及提交offset)就挂掉了,重启后会重新拿到已经消费但是还没通知(ACK)给MQ的消息,就产生了重复消费问题。(PS:这里如果MQ采用AUTO_ACK模式,消费者拿到消息后会第一时间给MQ做出ACK反馈,之后再去消费消息。但还没来得及消费完就挂掉了,那么MQ会认为此条消息处理过了,消费者重启后会继续从MQ拿消息,会产生消息丢失的问题,参看:<消息丢失问题>章节)
解决办法:
数据携带唯一id,基于内存Map或Set、Redis、数据库唯一键,保存消费成功的数据,重复数据来第二次时候可以感知到并直接丢弃处理。

面试遇到过这么一个问题:
面试官:有100w用户电话号码数据在数据库中,并不保证其中是否有重复,要求为每一个用户发一条短信并且不可重复发送,现提供一个基于http的短信发送微服务,该如何设计架构,最简单最快的把短信发送出去?

我的思路:
1.数据量过大,不能在数据库上做去重,并且用多线程读取更快。
2.短信服务会成为瓶颈,需要缓冲。
3.怎么在消费的过程中直接过滤掉重复消息。

我:用多线程读取数据(计算好多少页,每个线程取多少次,一次取多少条)将用户数据灌入MQ,多个消费者从MQ取得数据先在Redis中读一下看看有没有消费过,并在Redis中存入消费过的电话号码,调用短信服务(多部署几个)发送。重复电话号码直接舍弃数据不消费。
接着面试官问:如果就有那么几个消费者同时取到电话号码,巧了,他们取到的电话号码相同,查了一下Redis,没人发送过,就有重复发送的问题,怎么解决。
我:呃…


回来想了一下这个问题,因为先读取Redis再写入Redis并不能保证原子性,出现了并发问题。采用和分布式锁相同的思想来解决这个问题。参考下面文章:
Redis分布式锁的正确实现方式
用Redis的set加入NX(SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;)参数保证set原子性并且取得返回值。完美解决~~~


PS:

  1. Redis是主从集群,上述分布式锁会有漏洞。Redisson分布式锁实现框架可以弥补漏洞。(slave节点异步同步master数据,有延时。master节点设置锁还未同步到slave时挂掉了,锁就失效了)
  2. 由于本人技术水平有限,上述的架构方案并不是最优的,读文章小伙伴有更好的解决方案请发邮件给我,不胜感激~~~~

消息丢失问题

1.生产者发送消息丢失:
生产者发送消息到MQ过程中,网络原因丢了。
MQ接收到消息后内部出错,没保存下来。
解决办法:
1.用事务,发送时用try catch包裹发送消息代码块,如果发送失败可以拦截Exception再做进一步处理(重发或者…)
事务机制是同步阻塞的,影响发送消息吞吐量。
2.(RabbitMq)利用confirm机制。把channel设置成confirm模式,发送结果通知给本地实现的接口,如果通知失败再做进一步的处理(重发或者…) 异步不会阻塞,吞吐量高
2.MQ本身问题丢失:
MQ挂掉了导致内存中消息丢失
解决办法:配置持久化。但是也有一种可能,消息接收到了但是还没来得及持久化就挂掉了,还是会丢失一点点数据。
3.MQ到消费者消费过程中导致的丢失,消费端弄丢了数据
消费着拿到消息但是还没来得及消费就挂掉了,MQ以为消费者已经处理完了
解决办法:关闭AUTO_ACK,采用CLIENT_ACK模式,客户端收到消息处理成功后再手动发送ACK给MQ。如果消费者挂掉了,MQ针对这条消息会ACK超时,重新发给别人继续消费。

消息顺序性问题

这里假设要处理一个订单的三个任务,三个任务为一组:

  1. RabbitMQ等非分布式MQ解决方案:
    思路:将同订单任务按顺序放到一个Queue中,一个消费者只从一个Queue消费。
    高级JAVA开发 MQ部分
    这里不能建立无穷多个Queue,将订单号的hash值对Queue个数取余分发到对应Queue中即可。
  2. Kafka等分布式MQ解决方案:
    参考:kafka topic消息分配partition规则(Java源码)
    思路:由于Kafka是分布式的,每个Queue中的数据会分布到多个partition中。Kafka可以保证每个partition中的数据是有顺序的,那么指定key(比如订单ID),将需要保证顺序的任务放到同一partition中(kafka会把key值一样的任务放到一个partition中,和上述订单号对Queue个数取余原理相同),一个消费者只能从一个partition消费,这样就保证了按顺序执行。

如果消费者是多线程并发的从partition取得数据,多线程不能保证执行顺序,那么上述模型就不好用了。
解决办法: 在消费者端建立多个线程安全的队列(比如LinkedBlockingQueue,这里我认为采用阻塞队列比较好,省得消耗jvm内存过多导致oom异常),从MQ取得任务,将相同订单号的任务发送到同一个队列(也可以用hash取余的办法),再对每一个队列启动一个线程消费任务,保证顺序执行。
高级JAVA开发 MQ部分

消息过期丢失、大量积压等问题

  1. 过期问题:
    一般不设置消息的过期时间。如果设置了过期时间,只能在事后从数据源头找出数据,写程序将数据重新发送到MQ中。
  2. 大量积压后如何快速消费:
    ActiveMQ、RabbitMQ等非分布式MQ单个Queue数据量过大增加临时MQ节点也不能解决问题(每个节点存储全量数据),需要增加消费者临时节点来加速消费。
    Kafka是分布式MQ,每个消费者只能指定一个partition消费,那么新建立一个更多节点的Kafka集群,增加临时服务将原Kafka集群中的数据直接分发到新Kafka集群,这样消息会平均分配到更多的机器中,减缓MQ压力,再针对新Kafka集群添加实际业务处理的消费者,增加消费速度即可。
  3. MQ积压导致磁盘空间不足:
    (Kafka)增加临时消费节点将消息写到临时MQ集群中。
    或者增加临时消费者拿到数据直接将数据扔掉,事后做补偿。防止MQ直接压垮掉导致整个系统不可用。

如何保证MQ高可用性

RabbitMQ高可用以及部署模式

单机模式,普通集群模式,镜像集群模式

  1. 单机模式
  2. 普通集群模式
    集群中Master保存Queue元数据(Queue的属性之类的数据)和Queue数据,slave只保存Queue的元数据,在访问slave节点时,slave去和主节点通信取得Queue数据。
    总结:
    优点:提高消费吞吐量
    缺点:1. 性能开销大,在集群内部产生大量数据传输 2.可用性几乎没有保障,主节点挂掉了,整个MQ不可用。
  3. 镜像集群模式
    所有节点都保存Queue的所有数据。在写入数据时,各个节点自动同步。消费数据时同样。
    总结:
    优点:高可用,单个节点挂掉不影响整个集群
    缺点:1.性能开销大,需要所有机器同步所有数据。 2.不是分布式的,单Queue数据量超级大,超出单机最大容量时无法处理。 3.扩展性很差,和第2点一样,加机器也不能缓解数据量超级大的问题。

如何开启镜像集群模式

在管理控制台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点,也可以要求同步到指定数量的节点,然后再次创建Queue的时候应用这个策略,就会自动将数据同步到其他的节点上去了。

kafka的高可用性

多个broker组成,每个broker是一个节点;创建一个topic,这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition放一部分数据。(可以理解为每个Queue的数据被划分到不同机器上,换言说每个机器都持有同一个Queue的一部分数据)。在0.8版本以前没有HA机制,其中一台机器宕机则数据直接丢失。0.8版本后可以针对每个partition设置多个broker,其中一台是leader节点,其余是follower节点,只有leader节点提供对外读写服务,数据读写自动同步到follower节点,如果leader挂掉,follower们通过选举算法选举一个follower作为新的leader继续提供服务。

高级JAVA开发 MQ部分图片摘自中华石杉《Java工程师面试突击第1季(可能是史上最好的Java面试突击课程)\06_引入消息队列之后该如何保证其高可用性?\视频\04