RabbitMQ的基本使用

docker安装RabbitMQ

#先拉取下来
docker pull rabbitmq
#给rabbitmq创建容器并启动
docker run -d  -p 5671:5671 -p 5672:5672  -p 15672:15672 -p 15671:15671  -p 25672:25672  -v /data/rabbitmq-data/:/var/rabbitmq/lib  --name rabbitmq b8956a8129ef
#进入rabbitmq
docker exec -it rabbitmq sh
#运行
rabbitmq-plugins enable rabbitmq_management

#浏览器访问:http://ip地址:15672
#用户名密码:guest

添加用户

添加用户界面

Virtual Hosts管理 就相当于数据库
添加数据库
数据库权限

java连接RabbitMQ

引入maven依赖

<!-- 引入队列依赖 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.10</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
  • 生产者
  • 队列 rabbitmq
  • 消费者

简单队列(simple queue)

获取连接

public class ConnectionUtils {
//获取mq的连接
    public static Connection getConnection() throws IOException, TimeoutException {
//        定义一个连接工厂
        ConnectionFactory factory=new ConnectionFactory();
//        设置服务地址
        factory.setHost("39.107.221.63");
//        AMQP 5672
        factory.setPort(5672);
//        vhost
        factory.setVirtualHost("/vhost_hzy");
        factory.setUsername("user_hzy");
        factory.setPassword("root");
        return factory.newConnection();
    }
}

生产者

//生产者生产消息
public class Send {
    private static final String QUEUE_NAME="test_simple_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection=ConnectionUtils.getConnection();
//      从连接中获取一个通道
        Channel channel=connection.createChannel();
//      创建队列  声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        String msg="hello simple !";
//        负责生产消息
        channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
        System.out.println("--send msg:"+msg);
        channel.close();
        connection.close();
    }
}

消费者

//消费者获取消息
public class Recv {
    private static final String QUEUE_NAME="test_simple_queue";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel=connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//        定义消费者
        DefaultConsumer 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");
                System.out.println("new api recv:" + msg);
            }
        };
//        监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
/** 不推荐
//        定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
//        监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
        while(true){
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msgString=new String(delivery.getBody());
            System.out.println("{recv} msg:"+msgString);
        }
 */
    }
}

简单队列的不足
耦合性高,生产者11对应消费者(如果多个消费者消费队列中消息,就不行了;队列名变更 必须同时变更)

Work queues 工作队列

P------Queue------C1,C2

(轮询方式)
先创建一个获取连接的方法(简单队列中有)
生产者

public class Send {
    /**
     * P------Queue------C1,C2
     * */
    private static final String QUEUE_NAME="test_work_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
//        声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        for (int i = 0; i < 50; i++) {
            String msg="hello"+i;
            System.out.println("{work send}:"+msg);
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            try {
                Thread.sleep(i*20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        channel.close();
        connection.close();
    }
}

消费者1

public class Recv1 {
    private static final String QUEUE_NAME="test_work_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        final Channel channel = connection.createChannel();
//        声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        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");
                System.out.println("[1] Recv msg:"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1] done");
                }
            }
        };
        boolean autoAck=true;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

消费者2
复制消费者1代码进行修改

(公平分发 fair dipatch)

使用公平分发,必须关闭自动应答ack,改成手动

在生产者中生产消息之前加上

//        使用公平分发      每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者   1次只处理一个消息
        int prefetchCount=1;
        channel.basicQos(prefetchCount);

消费者1

public class Recv1 {
    private static final String QUEUE_NAME="test_work_queue";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        final Channel channel = connection.createChannel();
//        声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //公平分发
        channel.basicQos(1);//保证一次只分发一个

        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");
                System.out.println("[1] Recv msg:"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1] done");
// 公平分发                   手动回复消息
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
// 公平分发                  自动应答改为false
        boolean autoAck=true;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

消费者2同理

消息应答与消息持久化


  1. 消息应答

boolean autoAck=true;(自动确认模式)一旦rabbitmq将消息分发给消费者,就会从内存中删除
这种情况下,如果杀死正在执行的消费者,就会丢失正在处理的消息。
boolean autoAck=false;(手动确认模式),如果有一个消费者挂掉,就会交付给其他消费者,rabbitmq支持消息应答,消费者发送一个消息应答,告诉rabbitmq这个消息我处理完了,然后rabbitmq就删除内存中的消息
消息应答默认是false


  1. 消息持久化

//声明队列
boolean durable=true;
channel.queueDeclare(QUEUE_NAME,durable,false,false,null);
我们将程序中的boolean durable改成true,就实现了持久化,注意:如果更改已存在的队列会报错!!!
注意:rabbitmq不允许重新定义(不同参数)一个已存在的队列
解决方法:更改队列名或者删除已存在的队列,重新创建
持久化位置

订阅模式publist/subscribe

发布订阅

  1. 一个生产者,多个消费者
  2. 每一个消费者都有自己的队列
  3. 生产者没有直接把消息发送到队列,而是发到了交换机 转发器 exchange
  4. 每个队列都要绑定到交换机上
  5. 生产者发送的消息 经过交换机 到达队列 就能实现 一个消息被多个消费者消费

注册->邮件->短信…
生产者

public class Send {
    private static final String EXCHANGE_NAME="test_exchange_fanout";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel=connection.createChannel();
//        声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");//分类
//        发送消息
        String msg="hello ps";
        channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
        System.out.println("Send :"+msg);
        channel.close();
        connection.close();
    }
}

注意:交换机没有存储能力,只有队列有存储能力!
消费者1

public class Recv1 {
    private static final String QUEUE_NAME="test_queue_fanout_email";
    private static final String EXCHANGE_NAME="test_exchange_fanout";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        final Channel channel=connection.createChannel();
//        队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//        绑定队列到交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        //公平分发
        channel.basicQos(1);//保证一次只分发一个

        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");
                System.out.println("[1] Recv msg:"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1] done");
// 公平分发                   手动回复消息
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
// 公平分发                  自动应答改为false
        boolean autoAck=false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

消费者2同上

路由模式

Exchange(交换机 转发器)
一方面是接收生产者的消息,另一方面是向队列推送消息
匿名转发
Fanout(不处理路由键)
Direct(处理路由键)

生产者

public class Send {
    private static final String EXCHANGE_NAME="test_exchange_direct";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
//        exchange
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");
        String msg="hello direct!";
        String routingKey="info";//发送的key,绑定这人key的会收到
        channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
        System.out.println("[send]:"+msg);
        channel.close();
        connection.close();
    }
}

消费者1

public class Recv1 {
    private static final String EXCHANGE_NAME="test_exchange_direct";
    private static final String QUEUE_NAME="test_queue_direct_1";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        channel.basicQos(1);
//        绑定key,消费生产者中的key
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
        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");
                System.out.println("[Recv1]:"+msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[done1]");
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        boolean autoAck=false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);

    }
}

在消费者2中会绑定多个key
`// 绑定多个key,哪个key发送消息,就接受哪个key的消息

    channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
    channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
    channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"warning");`

主题模式

Topic exchange
将路由键和某模式匹配
#匹配一个或者多个
*匹配一个

生产者

public class Send {
    private static final String EXCHANGE_NAME="test_exchange_topic";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
//        exchange
        channel.exchangeDeclare(EXCHANGE_NAME,"topic");
        String msgString="商品....";
        channel.basicPublish(EXCHANGE_NAME,"goods.upd",null,msgString.getBytes());
        System.out.println("----send "+msgString);
        channel.close();
        connection.close();
    }
}

消费者和上面的订阅模式基本一样
区别:将订阅模式中的key改为

channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");

Rabbitmq的消息确认机制

问题:生产者将消息发送出去之后,消息到底有没有到达rabbitmq服务器,默认的情况是不知道的;
两种方式解决:

  • AMQP实现了事务机制

  • Confirm模式
    AMQP事务机制
    txSelect txCommit txRollback

  • txSelect 用于将当前channel设置成transation模式

  • txCommit 用于提交事务

  • txRollback 用于回滚事务
    生产者

public class TxSend {
    private static final String QUEUE_NAME="test_queue_tx";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel=connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        String tx_msg="hello tx message";
        try {
            channel.txSelect();
            channel.basicPublish("",QUEUE_NAME,null,tx_msg.getBytes());
            channel.txCommit();
        }catch (Exception e){
            channel.txRollback();
            System.out.println("执行了事务回滚!");
        }
    }
}

消费者

public class TxRecv {
    private static final String QUEUE_NAME="test_queue_tx";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel=connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        channel.basicConsume(QUEUE_NAME,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("[recv[tx] msg]"+new String(body,"utf-8"));
            }
        });
    }
}

Confirm模式

Confirm模式最大的好处在于他是异步
注意:队列不能重复设置AMQP事务和Confirm事务

Confirm单条消息
生产者

public class Send1 {
    private static final String QUEUE_NAME="test_queue_confirm";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel=connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        String tx_msg="hello confirm message";
//        设置confirm事务
        channel.confirmSelect();
        channel.basicPublish("",QUEUE_NAME,null,tx_msg.getBytes());
//        确认发送消息
        if(!channel.waitForConfirms()){
//        如果失败
            System.out.println("message send failed");
        }else{
            System.out.println("message send ok");
        }
        channel.close();
        connection.close();
    }
}

消费者同AMQP事务
Confirm多条消息

同时发送一批量的消息

public class Send2 {
    private static final String QUEUE_NAME="test_queue_confirm";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel=connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        String tx_msg="hello confirm message barch";
//        设置confirm事务
        channel.confirmSelect();
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("",QUEUE_NAME,null,tx_msg.getBytes());
        }
//        确认发送消息
        if(!channel.waitForConfirms()){
//        如果失败
            System.out.println("message send failed");
        }else{
            System.out.println("message send ok");
        }
        channel.close();
        connection.close();
    }
}

异步Confirm模式

public class Send3 {
    private static final String QUEUE_NAME="test_queue_confirm3";
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection= ConnectionUtils.getConnection();
        Channel channel=connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//        生产者调用confirmSelect将channel设置为confirm模式注意
        final SortedSet<Long> confirmSet= Collections.synchronizedSortedSet(new TreeSet<Long>());
//        通道添加监听
        channel.addConfirmListener(new ConfirmListener() {
//            没有问题的handleAck
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                if(b){
                    System.out.println("---handleAck---true");
                    confirmSet.headSet(l+1).clear();
                }else{
                    System.out.println("---handleAck---false");
                    confirmSet.remove(l);
                }
            }
            @Override
            public void handleNack(long l, boolean b) throws IOException {
                if(b){
                    System.out.println("---handleAck---true");
                    confirmSet.headSet(l+1).clear();
                }else{
                    System.out.println("---handleAck---false");
                    confirmSet.remove(l);
                }
            }
        });
        String msgStr="message str";
        while (true){
            long seqNo=channel.getNextPublishSeqNo();
            channel.basicPublish("",QUEUE_NAME,null,msgStr.getBytes());
            confirmSet.add(seqNo);
        }
    }
}

spring整合rabbitmq
spring整合rabbitmq

<!-- 引入队列依赖 -->
    <dependency>
      <groupId>com.rabbitmq</groupId>
      <artifactId>amqp-client</artifactId>
      <version>4.0.2</version>
    </dependency>

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.10</version>
    </dependency>

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.5</version>
    </dependency>

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.0.0.RELEASE</version>
    </dependency>
    <!-- RabbitMQ -->
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
        <version>1.7.5.RELEASE</version>
    </dependency>

xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/rabbit
    http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd" >
<!--    定义RabbitMQ的连接工厂-->
<rabbit:connection-factory id="connectionFactory" host="39.107.221.63" port="5672" username="guest" password="guest" virtual-host="/vhost_hzy"/>
<!--    定义Rabbit模板,指定连接工厂以及定义exchange-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" exchange="fanoutExchange"/>
<!--    MQ的管理,包括队列、交换器声明等-->
    <rabbit:admin connection-factory="connectionFactory"/>
<!--    定义队列,自动声明-->
    <rabbit:queue name="myQueue" durable="true"/>
<!--    定义交换器,自动声明-->
    <rabbit:fanout-exchange name="fanoutExchange">
        <rabbit:bindings>
            <rabbit:binding queue="myQueue"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>
<!--    队列监听-->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto">
        <rabbit:listener ref="foo" method="listen" queue-names="myQueue"/>
    </rabbit:listener-container>
<!--    消费者-->
    <bean id="foo" class="com.hzy.rabbitmq.spring.MyConsumer"/>
</beans>

生产者

        AbstractApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:spring-context.xml");
//        RabbitMQ模板
        RabbitTemplate template=applicationContext.getBean(RabbitTemplate.class);
//        发送消息
        template.convertAndSend("Hello-World!");
        Thread.sleep(1000);
        applicationContext.destroy();//容器销毁

消费者

    public void listen(String foo){
        System.out.println("消费者:"+foo);
    }

应用场景

在用户进行增加,删除,修改时,及时更改缓存中的数据!
当用户在数据库中增加,删除,修改时,告诉缓存(如redis)该更新数据了,然后进行更新!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值