Java for Web学习笔记(九三):消息和集群(8)RabbitMQ和消息模式(中)
来源:互联网 发布:编写sql语句的工具 编辑:程序博客网 时间:2024/06/07 11:14
Work Queues:竞争消费模式(competing consumers pattern)
简单模式,也可以用于多个client(subsriber)去获取消息,和简单模式对比:
- 我们希望数据能够真正被client处理完,client处理好后应发送Basic.ACK消息给server,如果client因为某个原因未能处理好,当channel关闭,connection关闭或中断的时候,这个消息应该能够分配给其他的client进行处理。
- 我们希望一次只获取一个消息(或者若干),而不是将队列中所有消息都取走,这样才能更好地进行负载均衡。
- 我们希望在server重启后,未处理的消息仍然在队列中。
- 我们还将学习添加AMQP的header。
发布的代码,忽略了channel和connection的关闭。可参见[1]
ConnectionFactory factory = new ConnectionFactory();factory.setHost("191.8.1.107");factory.setUsername("test");factory.setPassword("123456");Connection connection = factory.newConnection();Channel channel = connection.createChannel();//【3】参数2:durable true if we are declaring a durable queue (the queue will survive a server restart),这里满足需求:我们希望在server重启后,未处理的消息仍然在队列中。需要注意的是,如果已经声明的durable(true或者false),是不允许更改的,否则会报异常,也就是server中的queue一旦创建,这个属性是不能变更的。channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);//【4】参数3:添加Content-Type为text/plain作为AMQP的头,演示了如何设置AMQP的header。//【3】MessageProperties.PERSISTENT_TEXT_PLAIN表明消息是以文本的方式存放在磁盘来进行持续化。需要注意的是RabbitMQ的消息持续化并不很能确保,因此收到消息到写磁盘,有一定的时间差,如果这时候意外停掉,没有办法。如果需要很稳健地确保,需要某种方式向发布者返回确认,publish batches of messages and wait for outstanding confirms。channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
接收的代码,忽略了channel和connection的关闭。我们通过sleep一段时间,来模拟处理消息所耗时间。
ConnectionFactory factory = new ConnectionFactory();factory.setHost("191.8.1.107");factory.setUsername("test");factory.setPassword("123456");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);//【2】maximum number of messages that the server will deliver, 0 if unlimited,即每次只从server中获取一个消息。channel.basicQos(1);final Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); /* 小例子中,下面三个log的显示 * 11:11:33.903 [pool-2-thread-6] [INFO ] Envelope(deliveryTag=6, redeliver=false, exchange=, routingKey=task_queue-1) * 11:11:33.903 [pool-2-thread-6] [INFO ] #contentHeader<basic>(content-type=text/plain, content-encoding=null, headers=null, delivery-mode=2, priority=0, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null) * 11:11:33.903 [pool-2-thread-6] [INFO ] properties - ContentType : text/plain */ log.info("Envelope : {}",envelope); log.info("properties : {}",properties); log.info("properties - ContentType : {}",properties.getContentType()); try{ sleep(message.length()); //模拟异步处理消耗一定的时间 }finally{ log.info(" [x] Done"); //【1】向Server回复了一个Basic.ACK的消息 channel.basicAck(envelope.getDeliveryTag(), false); } }};//【1】参数2为false:告知server,client处理完后,回复一个ACKchannel.basicConsume(TASK_QUEUE_NAME, false, consumer);
我们可以在管理网页上,或者通过命令行来查看没有收到ack的消息
rabbitmqctl list_queues name messages_ready messages_unacknowledgedRabbitMQ server会以轮询的机制,将消息均匀地发送到各个client,即便某个client的处理能力强,很快回复了Basic.ACK,仍如还是按round-robin的方式,依次发送,因为server并不是等待Basic.ACK后,才发生,而是收到message后,就根据round-robin的轮询方式发送,在收到Basic.ACK后,将消息从队列中删除。如果我们很明确知道某个client的处理能力很强,可以利用channel.basicQos(2);,每次分发两个消息给它,相当于weight=2。
Publish/Subscribe: Sending messages to many consumers at once
事实上,producer并没有将消息写到队列,而是发送到Exchange,由exchange决定将消息放入某个队列,某些队列,亦或者丢弃这个消息。exchange有几种模式:direct, topic, headers和fanout。publish/subcribe模式使用的是fanout,即广播到绑定在该exchange的所有队列。
下面是发送和接收方的代码,同样的省略了channel和connection的关闭。也可参见[2]
发送方:
ConnectionFactory factory = new ConnectionFactory();factory.setHost("191.8.1.107");factory.setUsername("test");factory.setPassword("123456");Connection connection = factory.newConnection();Channel channel = connection.createChannel(); //创建一个类型为FANOUT的exchange,名字为EXCHANGE_NAME//在前面两个例子中,exchange的名字设置为"",即使用缺省的exchange,也称为nameless exchange,将消息路由到和routing_key名字相同的队列中channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);//发布到特定的exchange上,在fanout中,将忽略routint_key(队列名字),我们填入""即可。channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
接收方:
ConnectionFactory factory = new ConnectionFactory();factory.setHost("191.8.1.107");factory.setUsername("test");factory.setPassword("123456");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);/* 临时队列。本例将演示临时队列的生产(一个新的空队列,没有旧的消息),由server来给出随机的名字,并在client断开连接时,自动删除队列。不带参数的channel.queueDeclare()将满足这个需求。*/String queueName = channel.queueDeclare().getQueue(); //名字例子:amq.gen-JzTY20BRgKO-HjmUJj0wLg/* Binging:即告知exchange将消息发往某个queue。在fanout的方式下,exchange会将消息发往所有绑定的队列。*/channel.queueBind(queueName, EXCHANGE_NAME, "");Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); log.info(tag + " [x] Received '" + message + "'"); }};channel.basicConsume(queueName, true, consumer);
Routing:Receiving messages selectively
在发布/订阅模式上在进一步,队列只接受某发布的某类型消息。上图以log exchange为例,不同的队列接收不同的log级别。这就是路由模式。具体可参考[3]。
我们将重点学习exchange模式为direct时,队列不仅与exchange关联,还与具体的routing_key绑定。
我们通过代码进一步学习,同样的忽略channel和connection的关闭,另外,也忽略了创建connection和创建channel的代码,这些代码和之前的无异。
private String[] testRoutingKey = {"error","info","error","warning","info"};private void routingSend() throws Exception{ ... 创建connection和channel ... channel.exchangeDeclare(EXCHANGE_NAME, "direct"); for(int i = 0; i <5 ; i ++){ Thread.sleep(2_000L); String message = "message-" + i + "-" + testRoutingKey[i]; log.info(" [x] Sent '" + message + "'"); channel.basicPublish(EXCHANGE_NAME, testRoutingKey[i], null, message.getBytes("UTF-8")); }}private void routingRecv(String...routingKeys ) throws Exception{ ... 创建connection和channel ... //exchange类型为direct,即会根据routing_key来判断送往哪个队列。之前学习的发布订阅模式采用的fanout是广播类型的,只要和该exchange绑定的队列,都会送往,因此会忽略routing_key。 channel.exchangeDeclare(EXCHANGE_NAME, "direct"); String queueName = channel.queueDeclare().getQueue(); for(String routingKey : routingKeys){ //同时绑定了exchange和routing_key,exchange会将该routing_key的消息送往该队列 channel.queueBind(queueName, EXCHANGE_NAME, routingKey); } Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); log.info(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "', {}", properties); } }; channel.basicConsume(queueName, true, consumer); }
Topics: Receiving messages based on a pattern (topics)
topic模式和direct模式类似,但其提供了对routing_key的模糊匹配。routing_key的格式为A.B.C.D的格式,每个词中间用.分隔,模糊匹配支持*和#
- *表示一个词
- #表示零或者多个词
下面的代码和direct模式很是相似,同样的我们省略了channel和connection的关闭
发送的代码:
...略:创建connection和channel ...channel.exchangeDeclare(TOPIC_EXCHANGE_NAME, "topic");channel.basicPublish(TOPIC_EXCHANGE_NAME, "quick.orange.fox", null, message1.getBytes("UTF-8")); //将被发送到Q1channel.basicPublish(TOPIC_EXCHANGE_NAME, "lazy.pink.rabbit", null, message2.getBytes("UTF-8")); //将被发送到Q2
接收的代码,以Q2为例:
...略:创建connection和channel ... channel.exchangeDeclare(TOPIC_EXCHANGE_NAME, "topic");String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, TOPIC_EXCHANGE_NAME, "*.*.rabbit");channel.queueBind(queueName, TOPIC_EXCHANGE_NAME, "lazy.#");Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); log.info(tag + " [x] Received '" + envelope.getRoutingKey() + "':'" + message + "', {}", properties); }};channel.basicConsume(queueName, true, consumer);
相关链接: 我的Professional Java for Web Applications相关文章
- Java for Web学习笔记(九三):消息和集群(8)RabbitMQ和消息模式(中)
- Java for Web学习笔记(九二):消息和集群(7)RabbitMQ和消息模式(上)
- Java for Web学习笔记(九四):消息和集群(9)RabbitMQ和消息模式(下)
- Java for Web学习笔记(九五):消息和集群(10)利用RabbitMQ实现订阅和发布
- Java for Web学习笔记(八九):消息和集群(4)定制发布和订购
- Java for Web学习笔记(八六):消息和集群(1)一般性了解
- Java for Web学习笔记(八八):消息和集群(3)序列化Serializable
- Java for Web学习笔记(九一):消息和集群(6)利用websocket实现订阅和发布(下)
- Java for Web学习笔记(九十):消息和集群(5)利用websocket实现订阅和发布(上)
- Java for Web学习笔记(八七):消息和集群(2)应用内的publish和subscribe
- Java for Web学习笔记(四七):WebSocket(4)Java Client和二进制消息
- (九)RabbitMQ消息队列-通过Headers模式分发消息
- RabbitMQ五种消息队列学习(三)--Work模式
- rabbitMQ消息服务器学习笔记(java)1
- rabbitMQ消息服务器学习笔记(java)2 工作队列
- linux(deepin15.4)下部署集群RabbitMQ消息队列镜像模式(三)
- RabbitMQ(消息队列)私人学习笔记
- rabbitmq(三) 消息确认
- VINS mono 系统学习 四
- Redis的数据类型简介
- 5.4已知银行不同期限存款的年息利率,求其本利之和
- LintCode-【容易】9.Fizz Buzz问题
- 树莓派3B+ 安装计算机视觉库(OpenCV_2.4.9官方源)
- Java for Web学习笔记(九三):消息和集群(8)RabbitMQ和消息模式(中)
- POJ1625-Censored!
- VINS mono 系统学习 五
- 最小生成树之安慰奶牛
- 带你彻底搞定希尔排序是个什么情况
- 声明和定义的区别
- 分治法计算乘幂
- 习题6.2(2)计算1*2*3+3*4*5+...+99*100*101的值
- LeetCode.720 Longest Word in Dictionary