消息队列的使用和注意事项

rabbitmq、kafka、activemq、rocketmq之间区别?

activemq比较成熟,有较低概率丢数据吞吐量低,用异步和解耦可以用下。

rabbitmq吞吐量万级,只比kafka低些,消息时效性最低,微秒级别,基本不丢数据。

rocketmq吞吐量10万级,比rabbitmq高些,消息时效性ms级别

kafka吞吐量10万级,吞吐量最高,常用于大数据,日志分析

 

使用消息队列我们要搞清楚一下几个问题:

  1. 为什么使用消息队列?
  2. 使用消息队列有什么缺点?
  3. 消息队列如何选型?
  4. 如何保证消息队列是高可用的?
  5. 如何保证消息不被重复消费?
  6. 如何保证消费的可靠性传输?
  7. 如何保证消息的顺序性?

1.使用消息队列可以达到几个目的:解耦、异步、削峰

解耦:系统间耦合性太强,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。

异步:一些非必要的业务逻辑以同步的方式运行,太耗费时间。将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快相应速度

削峰:

传统模式的缺点:

  • 并发量大的时间,所有的请求直接怼到数据库,造成数据库连接异常

中间件模式的优点:

  • 系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。

2.使用消息队列会有的缺点

  • 系统可用性降低:你想呀,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加入个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性会降低
  • 系统复杂性增加:加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,刺痛复杂性增大。

3.消息队列的选择:

 

 

4.如何保证消息队列的高可用

5.如何保证消息不被重复消费

如何保证消息不被重复消费的前提是我们必须要知道是什么原因导致了消息被重复消费:网络传输等等故障,确认信息没有传送到消息队列(消息队列不清楚是否被消费再次将消费发给消费者)

如何解决这个问题呢?

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

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

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

6.如何保证消费的可靠性传输?

  • 生产者弄丢数据
  • 消息队列弄丢数据
  • 消费者弄丢数据

 生产者丢数据

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

transaction机制就是说,发送消息前,开启事务(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);  
                }  
            });  

消息队列丢数据

处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。

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

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

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

 

消费者丢数据

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

至于解决方案,采用手动确认消息即可

 

7.如何保证消息的顺序性

分析:其实并非所有的公司都有这种业务需求,但是还是对这个问题要有所复习。

回答:针对这个问题,通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中(kafka中就是partition,rabbitMq中就是queue)。然后只用一个消费者去消费该队列。
有的人会问:那如果为了吞吐量,有多个消费者去消费怎么办?

这个问题,没有固定回答的套路。比如我们有一个微博的操作,发微博、写评论、删除微博,这三个异步操作。如果是这样一个业务场景,那只要重试就行。比如你一个消费者先执行了写评论的操作,但是这时候,微博都还没发,写评论一定是失败的,等一段时间。等另一个消费者,先执行写评论的操作后,再执行,就可以成功。

总之,针对这个问题,我的观点是保证入队有序就行,出队以后的顺序交给消费者自己去保证,没有固定套路。

posted @ 2020-04-24 15:02  逝年的我们  阅读(999)  评论(0编辑  收藏  举报