分布式消息队列

1 为什么要使用消息队列

最主要得应用场景:解耦 异步 削峰

(1)解耦

传统模式:系统间得耦合度强 如系统A直接调用系统B系统C得代码,如果再有系统D接入,则系统A还要修改代码。

中间件模式:系统A将消息写入消息队列,系统B,系统C 订阅消息队列,如果再有系统D介入,直接订阅消息队列即可 系统A不必修改代码

(2)异步

传统模式:一些非必要得业务逻辑以同步得方式运行浪费时间

中间件模式:将消息写入消息队列 一些非必要得业务逻辑以异步得方式运行 提高响应速度

(3)削峰

传统模式:并发量大得时候所有请求全部到数据库,造成数据库连接异常

中间件模式:系统慢慢得按照数据库能处理得并发量从消息队列中慢慢拉去消息。在生产环境中这种短暂得高峰期积压是允许的。

2  使用消息队列的缺点

  (1) 系统的可用性降低

     消息队列会挂掉 一但挂掉 就会影响可用性

(2) 系统复杂性增加 

考虑一致性问题 保证消息不被重复消费 保证消息可靠传输

3  消息队列选型

四种主流消息队列:ActiveMQ RabbitMQ  RocketMQ  Kafka 

去ActiveMQ 社区看看MQ更新频率(几个月更新一次)

去RabbitMQ社区看看RabbitMQ的更新频率(一月两次)

建议:

(1)中小型软件公司,建议选RabbitMQ ,一方面erlang语言天生具备高并发的特性,而且它的管理界面用起来十分方便。(定制erlang开发少 但 rabbitMQ社区活跃 可以解决开发中遇到的bug 这点对于中小型公司来书十分重要,

中小型软件公司 数据量不是特别大(跟互联网公司比) 应首选功能比较完备的,所以kafka排除。

Rocketmq 是阿里出品,如果阿里放弃维护Rocketmq ,中小型公司一般抽不出人来进行rocketmq的定制化开发。

(2)大型软件公司,根据具体使用在RocketMq和kafka 之间二选一。一方面,大型软件公司具有足够的资金搭建分布式环境,也具备足够大的数据量,针对rocketMq,大型软件公司也可以抽出人手对rocketMQ 进行定制化开发(java开发)。至于kafka.根据业务场景的选择,如果有日志采集功能,可定是首选kafka了。具体该选那个,看使用场景

4 保证消息队列的高可用

以RocketMQ为例 他的集群就有多master模式、多master 多slave异步复制模式、多master多slave同步双写模式。


分布式消息队列_第1张图片


通讯过程:Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic服务的Broker Master 建立长连接,且定时向Broker发送心跳。Producer 只能将消息发送到 Broker Master,但是Consumer则不一样,它同时和提供Topic服务的Master和Slave 建立长连接,既可以从Broker Maser 订阅消息,也可以从Broker Slave 订阅消息。

以kafka为例


分布式消息队列_第2张图片

一个典型的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) 幂等性的操作 比如set  多次操作 等同一次操作

(3) 准备一个第三方介质,来做消费记录。以redis为例 给消息分配一个全局id,只要消费过该消息,将以K-V形式写入redis 那消费者开始消费前,先去redis中查询有没有消费记录即可。

6 保证消费的可靠性传输

从三个角度分析:生产者弄丢数据 消息队列东丢数据 消费者弄丢数据

RabbitMQ:

(1)生产者丢失数据

提供Transacton 和confir吗模式来确保生产者不丢消息

Transacton  模式发消息前开启事务(channel.txSelect()),然后发送消息,如果发送过程中有什么异常,事务就会回滚(channel,txRollback()),如果发送成功则提交事务(channel.txCommit()).然而缺点就是吞吐量下降了。因此上产上用confirm模式的居多。一但channel 进入confirm 模式,所有该信道上面发送的消息都将会指派一个唯一的ID(从1开始),一但消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使生产这知道消息已经到达消息队列了。如果RabbitMQ没有处理该消息,则会发送一个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)消费者丢数据

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

解决方案:采用手动确认消息即可

Kafka

Producer 在发布消息到某个partition时,先通过ZooKeeper 找到该Partition的leader,然后无论该Topic的Replication Tactor为多少(也即 该partition 有多少个Replica),Producer只将该消息发送到该Partition的Leader.Leader会将该消息写入其本地log.每个Follower都从Leader中pull数据。

针对上述情况得出分析

(1)生产者丢失数据

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

《1》第一个配置要在Producer端设置acks=all .这个配置保证了,follwer同步完成后,才认为i消息发送成功。

《2》在priducer端设置retries=MAX,一但写入失败,则无限重试

(2)消息队列丢失数据 

针对消息队列丢失数据的情况,无外乎就是,数据还没同步,leader就挂掉了,这时zookpeer会其他的follwer切换为leader,那数据就丢失了。针对这种情况,应该做两个配置。

《1》 replication。factor参数,这个值必须大于1 即 要求每个partition必须有两个副本

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

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

(3)消费者丢数据

这种情况一般是自动提交了offset,然后你处理程序过程中挂了。kafka以为你处理好了。

offset:指的是kafka的topic中的每个消费组消费的下标。简单的来说就是一条消息对应一个offset下标,每次消费数据的时候如果提交offset,那么下次消费就会从提交的offset加1 那里消费。offset 消费下标从0开始 ,如10条消费了7条并且提交了,那么kafka服务器记录的提交的offset的下表就是6,那么下次消费的时候offset就从7开始。

解决方案跟rabbitMq一样改成手动提交即可。

ActiveMQ

RocketMQ

7 保证消息的顺序性

通过某种算法,将需要保持先后顺序的消息放到同一个消息队列(kafka 就是partition,rabbitMq中就是queue)然后只用一个消费者去消费该队列。


注:本文来源于网络 作者孤独烟 如有侵权请联系我删除即可

你可能感兴趣的:(分布式消息队列)