RabbitMq的学习(二):Java创建简单的生产者,消费者

RabbitMq-Java-Client官方文档:http://www.rabbitmq.com/api-guide.html

官方文档说的很详细,但是再详细也要动手操作一番,毕竟动手操作的时候,才会给自己挖坑。

使用环境:

Erlang环境: 21.0.9

RabbitMq版本:3.7.8

jdk环境:1.8

如果代码无法连接到mq,可参照以下建议解决:

(1).服务器是否联网

(2).RabbitMq是否启动成功

(3).是否开放端口号(默认端口号5672);云服务器:配置下安全组,本地服务器:让防火墙开放该端口号。

一、引入maven架包

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.5.0</version>
</dependency>

二、Java代码

生产者代码:

package com.mq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeoutException;

public class ProducerTest {

    public static void main(String[] args) {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 连接
        Connection connection = null;
        // 通道
        Channel channel = null;
        try {
            // 第一种方式
//        factory.setHost("192.168.162.130"); // IP
//        factory.setPort(5672); // 端口号
//        factory.setVirtualHost("my_test"); // RabbitMq的虚拟服务器
//        factory.setUsername("testuser"); // 用户名
//        factory.setPassword("testuser"); // 密码

            // 第二中方式:amqp://用户名:密码@IP:端口号/虚拟服务器名称
            factory.setUri("amqp://testuser:testuser@192.168.162.130:5672/my_test");
            // 获取连接
            connection = factory.newConnection();
            System.out.println(connection);

            // 创建通道
            channel = connection.createChannel();
            System.out.println(channel);

            /**
             * 创建一个通道
             *
             * queue 队列名称
             * durable 是否申明持久队列,true:rabbitmq重启后继续存在
             * exclusive 是否独占,ture:只有该连接能访问
             * autoDelete 是否自动删除,true:服务器不再使用时,将自动删除该队列
             * arguments 队列其它参数
             */
            channel.queueDeclare("testQueue", true, false, false, null);

            String msg = "Test message";

            /**
             * 推送消息
             *
             * exchange 路由
             * routingKey 路由密钥
             * props 消息的头信息
             * body 消息主体
             */
            channel.basicPublish("", "testQueue", null, msg.getBytes("UTF-8"));

        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

消费者代码:

package com.mq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeoutException;

public class ConsumerTest {

    public static void main(String[] args) {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 连接
        Connection connection = null;
        // 通道
        Channel channel = null;

        try {
            // 设置URL
            factory.setUri("amqp://testuser:testuser@192.168.162.130:5672/my_test");
            // 获取连接
            connection = factory.newConnection();
            // 创建通道
            channel = connection.createChannel();

            // 创建消费对象
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag,
                                           Envelope envelope,
                                           AMQP.BasicProperties properties,
                                           byte[] body) throws IOException {
                    String msg = new String(body, "UTF-8");

                    //TODO 自己的业务
                    System.out.println("Received message : " + msg);

                    // 处理完业务之后,手动删除消息
//                    this.getChannel().basicAck(envelope.getDeliveryTag(), false);
                }
            };

            /**
             * 消费消息
             * queue 队列名称
             * autoAck true,消费端收到消息后,队列自动删除消息
             *         false,消费端收到消息后,需手动删除消息
             * callback 消费者对象
             */
            channel.basicConsume("testQueue", true, consumer);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、运行代码以及查看RabbitMq的后台

1,未运行代码之前

mq的连接数,通道数,队列数均没有,部分截图如下

2,执行ProducerTest.java之后

由于我代码里没有关闭通道(channel)和连接(connection),所以在mq的管理页面
能看到连接数,通道数。

下面是队列(queue)的截图:

Unacked:消费端已接受但未回执的消息数量
Total:消息总数
3,执行ConsumerTest.java之后

testQueue队列中信息如下显示:

可以看到消息已经被消费端消费了。

四、关于mq的消费确认机制

1,mq里消费确认机制有两种形式:

(1).消费端取到消息,队列自动删除消息(上面的代码就是属于这个机制)。

(2).消费端取到消息,队列等待消费端回执,之后再删除消息。

第2种形式的好处在于:消费端处理消息时,发生异常,不会导致该消息没处理完就被队列删除掉。

下面是应用ack机制好处的官方部分说明,说的很透彻了。


If a consumer dies (its channel is closed, connection is closed, or TCP connection is lost) without sending an ack, RabbitMQ will understand that a message wasn't processed fully and will re-queue it. If there are other consumers online at the same time, it will then quickly redeliver it to another consumer. That way you can be sure that no message is lost, even if the workers occasionally die.


2,Java中RabbitMq的ack

将ConsumerTest.java中的代码做如下修改:

channel.basicConsume("testQueue", false, consumer);

然后再运行下消费端代码,效果如图:

会看到有一个没有回执的消息。关闭消费端连接之后,如下图:

看到刚才那条消息已经回到未消费里面了。

我再把ConsumerTest.java中下面的代码注释取消掉

// 处理完业务之后,手动删除消息
this.getChannel().basicAck(envelope.getDeliveryTag(), false);

再运行代码,就会发现,消息已经从队列中删除了。图我就懒得截了。

五、总结

(1)是RabbitMq的ack机制,需要根据不同的应用场景选择不同的机制。

比如像注册成功时,发生提醒短信,就可以选择自动删除机制,

因为即使在发送过程中失败了,也不会影响业务流程最后的成功,

如果一定要发送提醒短信,可以采取其它方案,比如注册成功页面,显示一个按钮,用来补发提示短信。

比如像订单之类,订单支付完成之后,将订单号压入队列,分发给商家,让商家处理订单,

这个时候是不允许出现订单号处理失败的情况。

总之,不会影响业务最终结果的选自动删除,反之选ack。

(2)是采用ack机制时,可能会发生阻塞现象。

当一个channel连接到queue并拿到message时,若这个message没有回执(即让队列删除message),

那么这个channel会一直占有这个queue,当然这个channel也会一直处理queue里的message。

但处理的message一直没有回执的话,会产生大量的unacked,这个时候就需要进行干预了。

 

 

由于我也是RabbitMq的初学者,写的不好的地方或者错误的地方,请多多指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值