持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
hello world体验
最简单直接的方式,Rabbitmq是一个消息代理,它可以接收和转发消息。我们可以认为它是邮局,我们需要送信的时候将信放到邮箱,邮递员从邮箱中拿到后将信递送给收件人。
生产者发送一个消息到一个指定的queue,中间不需要任何exchange规则。消费者从queue中进行消费。
Producer示例代码:
//声明queue
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
Consumer示例代码:
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
注意:生产者,消费者和代理服务不一定就非在同一台主机上,可以在多台主机,当然,有些应用程序既可以是生产者又可以是消费者。
Work Queues工作队列
工作队列又名任务队列,适用于多个消费者,我们可以认为一个有邮局中存在多个邮递员,根据邮递员的区域和工作能力等因素来指定其中一个邮递员来送信(类似kafka同一个groupId的消息分发模式)。这个模式应该是最常用的模式。
生产者发送一个消息到指定的queue,服务器根据负载均衡来决定把消息发给指定的消费者来消费。
Producer示例代码:
/*
* 任务一般是 不能因为消息中间件的服务而被耽误的,所以durable设置成了true
* 这样,即使rabbitMQ服务挂了,这个消息也不会消失
*/
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
channel.basicPublish(
"",
TASK_QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("UTF-8")
);
Consumer示例代码:
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
channel.basicQos(1);
channel.basicConsume(TASK_QUEUE_NAME, false, consumer);
注意:
-
Consumer的autoAck字段设置的是false,这表示consumer不会自动确认,而要message处理完成了之后,再调用channel.basicAck来通知服务器已经消费了message。这样即使Consumer在消费message过程中出问题了,也不会造成message被忽略,因为没有确认的message会被服务器重新进行投递。但是,这其中也要注意一个很常见的BUG,就是如果所有的consumer都忘记调用basicAck()了,就会造成message被不停的分发,也就造成不断的消耗系统资源。这也就是Poison Message(毒消息)。 -
message的持久化。关键的message不能应为服务出现问题而被忽略。所有的queue不能被多次定义(指参数不同)。如果一个queue开始声明为持久化,后续声明为非持久化,最后结果还是持久化的。 -
默认的采用的消息轮询的模式,在所有
cousumer中轮流发送。这种方式,没有考虑消息处理的复杂度以及cousumer的消费能力。而我们可以采用,consumer可以向服务器声明一个prefetchCount(预处理值)。channel.basicQos(prefetchCount);表示当前这个consumer可以同时处理几个message。这样服务器在进行消息发送前,会检查这个consumer当前正在处理中的message(message已经发送,但是未收到consumer的basicAck)有几个,如果超过了这个consumer节点的能力值,就不再往这个consumer分发。这种模式,官方也指出还是有问题的,消息有可能全部阻塞,所有consumer节点都超过了能力值,那消息就阻塞在服务器上,这时需要自己及时发现这个问题,采取措施,比如增加consumer节点或者其他策略。