首页 > 编程知识 正文

消息中间件原理(开源中间件有哪些)

时间:2023-05-04 18:10:49 阅读:69162 作者:493

消息中间件的一般知识点

一、消息队列的主要作用是什么?

1 .消息队列特性:

无论业务如何,普适的消息队列组件都不需要考虑上层业务模式,只需要发布消息即可,而上层业务的不同模块需要根据消息队列中定义的规范进行通信。 FIFO,先送货先到达的保证是一个消息队列和一个buffer的本质区别。 灾难恢复是普适消息队列组件的一个重要基本特性,节点的动态添加和删除以及消息的持久化是支持灾难恢复能力的关键。 性能,这当然提高了消息队列的吞吐量,也提高了整个系统的内部通信效率。

2 .为什么需要消息队列:

如果系统中“生产”和“消耗”的速度和稳定性等因素不一致,则需要消息队列,作为抽象层填补双方的差异。 消息是在两台计算机之间传输的数据单位。 消息可以非常简单,例如只包含文本字符串。 它可以更复杂,也可以包含嵌入的对象。 消息将发送到队列。 消息队列是在消息传输过程中保存消息的容器。

使用场景:

业务系统触发短信发送申请,但短信发送模块速度跟不上,需要临时保存来不及处理的短信,缓解压力。 您可以将短信发送申请置于消息队列中,直接回复给用户,使其成功。 短信发送模块可以慢慢地去消息队列取消息进行处理。

远程系统的订单成本高,并且因为网络等因素不稳定,所以汇总后发送。

任务处理系统首先接收用户的任务请求并存储在消息队列中,然后在后端打开多个APP应用程序,从队列中取出任务进行处理。

3 .使用消息队列的好处(消息队列的作用) :

异步:假设存在系统a调用系统b的系统调用链接。 通常需要20ms。 系统b调用系统c,通常需要200ms; 系统c调用系统d,通常需要2s。 由于一个用户的请求来得很慢,走一个链路需要2220ms,因此如果业务流程支持异步,可以考虑断开并异步从系统c到系统d的调用,然后同步而不放入链路,依次进行调用这样,实现想法的就是系统A -系统B -系统c,直接花了220ms直接成功了。 然后,系统c向MQ中间件发送消息,在系统d消耗了消息之后,系统c缓慢异步地执行这个时间2s的业务处理。 这直接将核心链路的性能提高了10倍。

取消合并:假设有系统a。 这个系统a生成核心数据。 现在下游需要系统b和系统c。 那很简单,系统a直接调用系统b和系统c的接口发送数据就好了。 但是,现在系统d、系统e、系统f、系统g等来了,其他10个系统会慢慢需要这个核心数据吗? 那么,负责系统a的人受够了。 然后,如果一个下游系统突然瘫痪怎么办? 系统a的调用代码有异常吗? 接到警告说那个系统a的同学会异常,结果他又去care是下游哪个系统瘫痪了? 因此,在实际的系统架构设计中,如果采用所有这种系统耦合方式,在某些场合是绝对不合适的,系统耦合度严重。 另外,相互耦合不是核心链路的调用,上述数据消耗等非核心场景会引起系统耦合,严重影响上下游系统的开发和维护效率。 因此,在上述系统架构中,可以采用MQ中间件实现系统解耦,系统a将自己的核心数据发送到MQ,下游哪个系统感兴趣自己消耗即可,不需要的话取消数据的消耗

消峰)假设有一个系统。 通常情况下每秒可能会收到数百个请求,但通常的处理是可以的。 qldch期一下子来了每秒数千个请求,在弹唱期间出现了流量qldch。 此时,我们可以使用MQ中间件进行流量削峰。 在每台机器前配置一层MQ,平时每秒有上百个请求,大家也能方便地收到消息。 到了瞬时qldch期,每秒数千个请求一下子涌入,可以滞留在MQ中。 然后,那台机器会慢慢处理和消耗。 qldch期间过后,消耗一段时间,MQ中积累的数据就会被消耗掉。 这是典型的MQ用法,在有限的机器资源上承载高并发请求。 允许在业务场景中异步去峰,qldch期将一些请求存入MQ,然后qldch期过去,后台系统在一定时间内消费完不存了,适合使用该技术方案。

4 .消息团队的缺点:

系统可用性降低:部署在系统上的外部依赖项越多,就越容易锁定。 本来a系统调用BCD三个系统的接口就好了,人ABCD的四个系统会好好添加MQ进入。 万一MQ死机,整个系统就会崩溃。

提高系统复杂性:如何强制添加MQ以确保消息没有重复消耗? 如果信息丢失了该怎么办? 如何保证消息传递的顺序性?

问题: A系统处理完毕后,很快就回成功了。 人都认为你的这个要求成功了。 但是,问题是,在BCD的三个系统中,如果BD的两个系统成功写入了库,从而导致c系统写入库失败,则该数据将不一致。

)二)如何确保消息队列的高可用性?

1.RabbitMQ高可用性

独立模式: Demo级别,不在生产环境中使用

正常集群模式:

当同时部署多个RabbitMQ服务器且生产者向RabbitMQ集群发送消息时,消息中存在元数据(例如消息描述信息),接收消息的MQ将消息的元数据信息发送到其他节点消费者从其中一台服务器获取消息时,如果当前服务器上存在消息的数据信息,则获取成功;如果不存在,则根据元数据信息从其他节点获取消息数据。 这样做也没有消息数据

保证MQ的高可用,因为存在消息数据的服务器挂掉,消息一样不存在,这样做只能保证MQ的吞吐量比较大。

采用镜像集群模式:

你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。  这样的话,好处在于,你任何一个机器宕机了,别的机器都可以用。坏处在于

性能开销大,消息同步所有机器,导致网络带宽压力和消耗很重!

没有扩展性,如果某个queue负载很重,你加机器,新增的机器也包含了这个queue的所有数据,并没有办法线性扩展你的queue

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

2.kafka的高可用

kafka一个最基本的架构认识:多个broker组成,每个broker是一个节点;你创建一个topic,这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition就放一部分数据。

这就是天然的分布式消息队列,就是说一个topic的数据,是分散放在多个机器上的,每个机器就放一部分数据。kafka 0.8以前,是没有HA机制的,就是任何一个broker宕机了,那个broker上的partition就废了,没法写也没法读,没有什么高可用性可言。kafka 0.8以后,提供了HA机制,就是replica副本机制。每个partition的数据都会同步到其他机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都跟这个leader打交道,然后其他replica就是follower。写的时候,leader会负责把数据同步到所有follower上去,读的时候就直接读leader上数据即可。只能读写leader?很简单,要是你可以随意读写每个follower,那么就要care数据一致性的问题,系统复杂度太高,很容易出问题。kafka会均匀的将一个partition的所有replica分布在不同的机器上,这样才可以提高容错性。

写数据的时候,生产者就写leader,然后leader将数据落地写本地磁盘,接着其他follower自己主动从leader来pull数据。一旦所有follower同步好数据了,就会发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)

消费的时候,只会从leader去读,但是只有一个消息已经被所有follower都同步成功返回ack的时候,这个消息才会被消费者读到。

三:高并发情况下消息队列满了如何防止消息丢失?

其实这个防止消息丢失,每种MQ都要从三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据,以RabbitMQ为例:

1.生产者丢数据:

从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。transaction机制就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。然而缺点就是吞吐量下降了。因此,生产上用confirm模式的居多。一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了.如果rabiitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。

2.消息队列丢数据

处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步:

将queue的持久化标识durable设置为true,则代表是一个持久的队列

发送消息的时候将deliveryMode=2

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

3.消费者丢数据

消费者丢数据一般是因为采用了自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时rahbitMQ会立即将消息删除,这种情况下如果消费者出现异常而没能处理该消息,就会丢失该消息。至于解决方案,采用手动确认消息即可。

四:消费者消费消息,如何保证MQ幂等性(消息不被重复消费)?

1.幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品使用约支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条

2.MQ消息发送

发送端MQ-client(消息生产者:Producer)将消息发送给MQ-server;

MQ-server将消息落地(持久化存到数据库中);

MQ-server回ACK给MQ-client(Producer);

MQ-server将消息发送给消息接受端MQ-client(消息消费者:Customer);

MQ-client(Customer)消费接受到消息后发送ACK给MQ-server;

MQ-server将落地消息删除

3.消息重复发送原因

为了保证消息必达,MQ使用了消息超时、重传、确认机制。使得消息可能被重复发送,如上图中,由于网络不可达原因:3和5中断,可能导致消息重发。消息生产者a收不到MQ-server的ACK,重复向MQ-server发送消息。MQ-server收不到消息消费者c的ACK,重复向消息消费者c发消息。

4.MQ内部如何做到幂等性的

对于每条消息,MQ内部生成一个全局唯一、与业务无关的消息ID:inner-msg-id。当MQ-server接收到消息时,先根据inner-msg-id判断消息是否重复发送,再决定是否将消息落地到DB中。这样,有了这个inner-msg-id作为去重的依据就能保证一条消息只能一次落地到DB。

5.消息消费者应当如何做到幂等性

对于非幂等性业务且要求实现幂等性业务:生成一个唯一ID标记每一条消息,将消息处理成功和去重日志通过事物的形式写入去重表。

对于非幂等性业务可不实现幂等性的业务:权衡去重所花的代价决定是否需要实现幂等性,如:购物会员卡成功,向用户发送通知短信,发送一次或者多次影响不大。不做幂等性可以省掉写去重日志的操作。

6.结合业务思考

业务表添加约束条件 如果你的数据库将来都不会分库分表,那么可以在业务表字段加上唯一约束条件(UNIQUE),这样相同的数据就不会保存为多份。

将处理后的消息写数据库(主键唯一),你先根据主键查一下,如果这数据已经存在,就不再消费。

使用 redis 如果你的系统是分布式的,又做了分库分表,那么可以使用 redis 来做记录,把消息 id 存在 redis 里,下次再有重复消息 id 在消费的时候,如果发现 redis 里面有了就不能进行消费。

基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。

五:如何保证消息的有序性?

1.rabbitMQ

rabbitmq可能出现顺序问题

生产者发送的消息是有序的,通过消息队列分发到不同的消费者上,但消费者消费的顺序不一致,可能导致消息的顺序就不一致。

一个消费者对应一个queue,消息只发送到一个queue中,即可保证消息的顺序

2.kafka

1个topic,3个partition,3个consumer,每个消费者消费一个partition,需要保证顺序的消息都放入同一个partiton,但是如果一个消费者开启多个线程来处理,还是无法保证消息的顺序性。

每个消费者内部设置多个内存队列,对消息的key做hash,将需要保证顺序的消息映射到同一个内存队列中,每个线程负责处理一个内存队列。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。