kafka、rabbitmq等消息中间件的工作原理、内部架构图、处理消息的重发机制、处理消息的丢失机制、消息的顺序性机制

问题1:为什么要使用消息队列?
回答:主要是解耦、异步、削峰,下面介绍这三种功能。
1、解耦

传统模式:
在这里插入图片描述
传统模式的缺点:
系统间耦合性太强,如上图所示,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!

中间件模式:
在这里插入图片描述
中间件模式的的优点:
将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。

2、异步

传统模式:
在这里插入图片描述
传统模式的缺点:
一些非必要的业务逻辑以同步的方式运行,太耗费时间。

中间件模式:
在这里插入图片描述
中间件模式的的优点:
将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度。

3、削峰
传统模式
在这里插入图片描述
传统模式的缺点:
并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常

中间件模式:
在这里插入图片描述
中间件模式的的优点:
系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。

问题2:使用了消息队列会有什么缺点?
回答:系统可用性降低、系统复杂性增加;系统可用性降低是指你的系统加一个消息中间件,那消息队列挂了,你的系统将会受到严重的影响,甚至导致整个系统无法正常的提供服务;系统的复杂性增加是指引入了消息中间件,必须考虑数据的一致性问题、消息的重复消费,消息的可靠传输(传输的过程中不丢失)等一系列问题,所以要结合自己的业务场景去引入相关的技术,脱离业务的架构都是耍牛氓。

问题3:消息队列如何选型?
回答:既然在项目中用了MQ,肯定事先要对业界流行的MQ进行调研,如果连每种MQ的优缺点都没了解清楚,就拍脑袋依据喜好,用了某种MQ,还是给项目挖坑,
下面列出了业界比较流行的消息中间件的对比,结合自己的业务合理选择相关的消息中间件。
在这里插入图片描述
一般针对中小型软件公司建议使用rabbitmq就足够了,因为rabbitmq的管理界面极其丰富,并且erlang语言天生具有极高的并发量,中小型公司不是说不能使用rocketmq、kafka,结合自己的业务场景合理选择即可,对于数据量不高的公司如果没有日志采集等相关的业务,建议不需要使用kafka,但是rabbitmq也有缺陷,难以进行二次维护和开发,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?
所幸,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要,kafka的mq功能不怎么完备,如果你的业务要求首选功能比较完备,建议不要使用kafka。不考虑rocketmq的原因是,rocketmq是阿里出品并使用java语言编写的消息中间件,如果阿里放弃维护rocketmq,中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐。

上面说的只是针对中小型软件公司选型消息中间件的条件,下面说针对大型互联网公司如何选型消息中间件,一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量,针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的,至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。

问题4:如何保证消息队列的高可用?
回答以rcoketMQ为例,他的集群模式就有多master 模式、多master多slave异步复制模式、多 master多slave同步双写模式。

多master多slave模式部署架构图
在这里插入图片描述
觉得和kafka好像,只是NameServer集群,在kafka中是用zookeeper代替,都是用来保存和发现master和slave。

通信过程如下:
Producer 与 NameServer集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。

Producer 只能将消息发送到 Broker master,但是 Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息。

那么kafka呢,为了对比说明直接上kafka的拓补架构图:
在这里插入图片描述
如上图所示,一个典型的Kafka集群中包含若干Producer(可以是web前端产生的Page View,或者是服务器日志,系统CPU、Memory等),若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),若干Consumer Group,以及一个Zookeeper集群。

Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行rebalance。

Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。

至于rabbitMQ,也有普通集群和镜像集群模式。

问题5:如何保证消息不被重复消费?即如何保证消息队列的幂等性?
回答:正常情况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offset的概念,对于kafka,每一个消息都有一个offset,kafka消费过消息后,需要提交offset,让消息队列知道自己已经消费过了。

造成重复消费的原因是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。

那么怎么避免因为网络抖动等原因造成的消息的重复消费问题呢?根据不同的业务场景有不同的处理方式。

1、你拿到这个消息做数据库的insert操作。
那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

2、你拿到这个消息做redis的set的操作。
那就容易了,不用解决。因为你无论set几次结果都是一样的,set操作本来就算幂等操作。

3、如果上面两种情况还不行,上大招。
准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。

问题6:如何保证消费的可靠性传输?
回答:我们在使用消息队列的过程中,应该做到消息不能多消费,也不能少消费。如果无法做到可靠性传输,可能给公司带来千万级别的财产损失。

其实这个可靠性传输,每种MQ都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据。

RabbitMQ

1、生产者丢数据
从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。

transaction机制就是说,发送消息前,开启事物(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。

由于有事务的存在,缺点就是吞吐量下降了。因此,生产上用confirm模式的居多。

一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始)。

一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID)

这就使得生产者知道消息已经正确到达目的队列了.如果rabiitMQ没能处理该消息,则会发送一个Nack消息给生产者,告诉生产者可以进行重试操作。

处理Ack和Nack的代码如下所示:

channel.addConfirmListener(new ConfirmListener() {

    @Override

    public void handleNack(long deliveryTag, boolean multiple) throws IOException {

        System.out.println("nack: deliveryTag = "+deliveryTag+" multiple: "+multiple);

    }

    @Override

    public void handleAck(long deliveryTag, boolean multiple) throws IOException {

        System.out.println("ack: deliveryTag = "+deliveryTag+" multiple: "+multiple);

    }

});

2、消息队列丢数据
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。

这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。

这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。

那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步:

1、将queue的持久化标识durable设置为true,则代表是一个持久的队列
2、发送消息的时候将deliveryMode=2

这样设置以后,rabbitMQ就算挂了,重启后也能恢复数据。

3、消费者丢数据
消费者丢数据一般是因为采用了自动确认消息模式。

这种模式下,消费者会自动确认收到信息。这时rahbitMQ会立即将消息删除,这种情况下如果消费者出现异常而没能处理该消息,就会丢失该消息。

至于解决方案,采用手动确认消息即可,或者使用死信队列存放消费失败的消息。

上面说了rabbitmq的消息不丢失的原因与解决方案,下面说一下kafka是怎么避免消息的不丢失的。

kafka
在这里插入图片描述
Producer在发布消息到某个Partition时,先通过ZooKeeper找到该Partition的Leader

然后无论该Topic的Replication Factor为多少(也即该Partition有多少个Replica),Producer只将该消息发送到该Partition的Leader。

Leader会将该消息写入其本地Log。每个Follower都从Leader中pull数据。
针对上述情况,得出如下分析。

1、生产者丢数据
在kafka生产中,基本都有一个leader和多个follwer。follwer会去同步leader的信息。
因此,为了避免生产者丢数据,做如下两点配置:

第一个配置要在producer端设置acks=all。这个配置保证了,follwer同步完成后,才认为消息发送成功。
在producer端设置retries=MAX,一旦写入失败,这无限重试

2、消息队列丢数据
针对消息队列丢数据的情况,无外乎就是,数据还没同步,leader就挂了,这时zookpeer会将其他的follwer切换为leader,那数据就丢失了。

针对这种情况,应该做两个配置。
replication.factor参数,这个值必须大于1,即要求每个partition必须有至少2个副本

min.insync.replicas参数,这个值必须大于1,这个是要求一个leader感知到有至少一个follower还跟自己保持联系

这两个配置加上上面生产者的配置联合起来用,基本可确保kafka不丢数据

3、消费者丢数据
这种情况一般是自动提交了offset,然后你处理程序过程中挂了。kafka以为你处理好了。
offset:指的是kafka的topic中的每个消费组消费的下标。
简单的来说就是一条消息对应一个offset下标,每次消费数据的时候如果提交offset,那么下次消费就会从提交的offset加一那里开始消费。

比如一个topic中有100条数据,我消费了50条并且提交了,那么此时的kafka服务端记录提交的offset就是49(offset从0开始),那么下次消费的时候offset就从50开始消费。

解决方案也很简单,改成手动提交offset即可。

问题7:如何保证消息的顺序性?
回答:通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中(kafka中就是partition,rabbitMq中就是queue)。然后只用一个消费者去消费该队列。

有的人会问:那如果为了吞吐量,有多个消费者去消费怎么办?

这个问题,没有固定回答的套路。比如我们有一个微博的操作,发微博、写评论、删除微博,这三个异步操作。如果是这样一个业务场景,那只要重试就行。

比如一个消费者先执行了写评论的操作,但是这时候,微博都还没发,写评论一定是失败的,等一段时间。等另一个消费者发布微博,然后当前消费者针对另一个消费者发布的微博执行写评论,就可以成功。

保证入队有序就行,出队以后的顺序交给消费者自己去保证,没有固定套路。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值