Rabbitmq工作模式的简述以及Demo

Rabbitmq的工作模式

简单模式

一个生产者,一个消费者,生产者发送消息,消费者接收消息,服务器不是持久的保存消息,消费者处理完,消息就会消除

连接rabbitmq的依赖

<dependencies>
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.4.3</version>
    </dependency>
</dependencies>
package m1;

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

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * rabbitmq 简单模式
 * 15672 管理控制台端口
 * 5672 访问端口
 */
public class Producer {


    public static void main(String[] args) throws IOException, TimeoutException {

        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.140");
        f.setPort(5672);
        f.setUsername("guest");
        f.setPassword("guest");
        Connection connection = f.newConnection();
        Channel c = connection.createChannel();

        //创建队列helloworld
        c.queueDeclare("helloworld",false,false,false,null);
        //向helloword队列发送消息
        c.basicPublish("","helloworld",null ,"Hello world!".getBytes());
        //断开连接
        c.close();
    }
}

运行后,访问http://192.168.64.140:15672/#/
在这里插入图片描述

在运行consumer

package m1;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 消费者
 */
public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.140");
        f.setPort(5672);
        f.setUsername("guest");
        f.setPassword("guest");
        Connection connection = f.newConnection();
        Channel c = connection.createChannel();

        //创建helloworld队列,已经存在不会创建
        c.queueDeclare("helloworld",false,false,false,null);

        //创建回调对象
        DeliverCallback deliverCallback = (consumerTag,message) ->{
            byte[] a = message.getBody();
            String s = new String(a);
            System.out.println("收到的消息"+s);
        };
        CancelCallback cancelCallback = consumerTag ->{

        };
        //接收消息,消息会传递到一个回调对象进行处理,
       
           /*
        true, Auto Acknowledgement
         autoAck = true 自动确认
        autoAck = false 手动确认   
       */
        c.basicConsume("helloworld",true,deliverCallback,cancelCallback);
    }

}

ready变成了0

在这里插入图片描述

工作模式

生产者发送5条消息,

消费者会同时接收消息

消费者1接收1 ,

消费者2接收2 ,

消费者3 接收3 ,

然后消费者1 在接收 4

消费者2在接收 5

  • 生产者发送消息代码
package m2;

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

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;

/**
 * 生产者
 */
public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {
        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.140");
        f.setPort(5672);
        f.setUsername("guest");
        f.setPassword("guest");
        Connection connection = f.newConnection();
        Channel c = connection.createChannel();

        //创建helloworld队列,已经存在不会创建
        c.queueDeclare("helloworld",false,false,false,null);

        while(true){
            System.out.println("输入消息");
            String s = new Scanner(System.in).nextLine();
            c.basicPublish("","helloworld",null ,s.getBytes());
        }

    }

}
  • 运行截图
    在这里插入图片描述

  • 消费者接收消息,其中使用了"."阻塞消息发送,先看最简单的接收

package m2;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 消费者
 */
public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.140");
        f.setPort(5672);
        f.setUsername("guest");
        f.setPassword("guest");
        Connection connection = f.newConnection();
        Channel c = connection.createChannel();

        //创建helloworld队列,已经存在不会创建
        c.queueDeclare("helloworld", false, false, false, null);

        //创建回调对象
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String s = new String(message.getBody());
            System.out.println("收到:" + s); //"sdf.ew....dad..// "
            //处理收到消息的对象,遍历字符串找'.',每找到一个暂停1秒,模拟耗时消息
            for (int i = 0; i < s.length(); i++) {
                if ('.' == s.charAt(i)) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            System.out.println("消息处理完成...............");
        };
        CancelCallback cancelCallback = consumerTag -> {

        };
        //接收消息,消息会传递到一个回调对象进行处理,
        c.basicConsume("helloworld", true, deliverCallback, cancelCallback);
    }

}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

每个消费者接受到生产者消息的数量都是一致的,但是也有新的问题,就是,如果其中一条消息堵塞还会不会,和上面的结果一样?结果其实是一样的,还是接收同等数量的消息,可以去利用上面的Demo进行尝试。

然后,就会产生新的问题,就是消费者1接受到的第一条消息被堵塞就会影响,接下来的消息接收,但是,剩下的两个消费者都是空闲状态,但是,消息都被堵塞到了消费者1中,极大的影响了消息的传递的效率。这是因为服务器不知到这件那个消费者处理完和没处理完,所以就没办法合理发送。

解决办法:

可以在消费者处改成autoAck=false 手动确认模式,然后在设置qos=1,每次只处理一条消息,处理完之前不会再处理新的消息

/*
这个在消息处理完成后添加
*/
System.out.println("消息处理完成...............");
//发送回执
// c.basicAck(回执,是否同时确认之前未确认的额消息);
c.basicAck(message.getEnvelope().getDeliveryTag(),false);

// 在c.basicConsume添加
 c.basicQos(1);
        //接收消息,消息会传递到一个回调对象进行处理,
  c.basicConsume("helloworld", false, deliverCallback, cancelCallback);

改完之后就不会再试轮循的方式了.

新的问题又出现了,然后就是如果每条消息,都加上很多的.,那么所有的消费者都在等待,第一条和第二条消息在等待,其他的消息在就绪(在内存中保存),如果服务器崩溃的话,这些在内存中的消息就会丢失.所以要解决的问题就是要将这些消息持久化存储到磁盘.

  • 队列的持久化,消息的持久化

    将 生产者和消费者

    c.queueDeclare("helloworld",false,false,false,null);
    

    更改为

    c.queueDeclare("taskqueue", true, false, false, null);
    

    还有这个

    c.basicConsume("taskqueue", false, deliverCallback, cancelCallback);
     //生产者
    c.basicPublish("","taskqueue", MessageProperties.PERSISTENT_BASIC,s.getBytes());
    

群发模式

中间需要交换机的使用,生产者发消息到交换机,然后交换机会与消费者连接,每个消费者都有自己的队列,在进行特定的分发

交换机:

  • direct
  • fanout(扇形交换机,像电风扇,做群发,每个消费者都能收到这个消息)
  • topic
  • headers

首先生产者创建交换机

package m3;

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

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;

/**
 * 生产者
 */
public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {

        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.140");
        f.setPort(5672);
        f.setUsername("guest");
        f.setPassword("guest");
        Connection connection = f.newConnection();
        Channel c = connection.createChannel();

        //创建fanout交换机:logs
        //如果存在才不会重建
        c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
        //向交换机发送消息

        while (true){
            System.out.print("输入消息:");
            String  s = new Scanner(System.in).nextLine();
            c.basicPublish("logs","",null,s.getBytes());
        }
    }
    
}

消费者部分代码,用UUID设置队列名称

package m3;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;

public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.140");
        f.setPort(5672);
        f.setUsername("guest");
        f.setPassword("guest");
        Connection connection = f.newConnection();
        Channel c = connection.createChannel();//通信的通道

        //创建队列,创建交换机,绑定
        //创建随机命名的队列
        String queue = UUID.randomUUID().toString();
        c.queueDeclare(queue,false,true,true,null);
        //创建fanout交换机:Logs
        c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
        //绑定,第三个参数对fanout交换机无效
        c.queueBind(queue,"logs","");

        DeliverCallback  deliverCallback = (consumerTag,message) ->{
            String s = new String(message.getBody());
            System.out.println("收到:"+s);

        };
        CancelCallback cancelCallback = consumerTag->{
        };
        c.basicConsume(queue,true,deliverCallback,cancelCallback);
        
    }

}

路由模式

用消息上的关键词与绑定的关键词匹配,就可以找到绑定的关键词,在根据路由键(info,error,warning)对应的消息队列,对应的消费者才能接收消息.

使用的交换机是direct

  • 生产者的代码

    package m4;
    
    import com.rabbitmq.client.BuiltinExchangeType;
    import com.rabbitmq.client.Channel;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.ConnectionFactory;
    
    import java.io.IOException;
    import java.util.Scanner;
    import java.util.concurrent.TimeoutException;
    
    public class Producer {
    
    
        public static void main(String[] args) throws IOException, TimeoutException {
    
            //连接
            ConnectionFactory f = new ConnectionFactory();
            f.setHost("192.168.64.140");
            f.setPort(5672);
            f.setUsername("guest");
            f.setPassword("guest");
            Connection connection = f.newConnection();
            Channel c = connection.createChannel();
    
            //创建fanout交换机:logs
            //如果存在才不会重建
            c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
            //向交换机发送消息
    
            while (true){
                System.out.print("输入消息:");
                String  s = new Scanner(System.in).nextLine();
    
                System.out.print("输入路由键:");
                String  k = new Scanner(System.in).nextLine();
                //第二个参数是路由键
                //前两种模式使用默认交换机,默认交互机使用队列名作为路由键
                c.basicPublish("direct_logs",k,null,s.getBytes());
            }
        }
    }
    

对应的关键词,消费者才能接收到消息

  • 消费者部分代码
package m4;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.TimeoutException;

/**
 * 消费者
 */
public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.140");
        f.setPort(5672);
        f.setUsername("guest");
        f.setPassword("guest");
        Connection connection = f.newConnection();
        Channel c = connection.createChannel();//通信的通道

        //创建队列,创建交换机,绑定
        //创建随机命名的队列
        String queue = UUID.randomUUID().toString();
        c.queueDeclare(queue,false,true,true,null);
        //创建direct交换机:direct_logs
        c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
        System.out.println("输入绑定键,用空格隔开:");
        String s1 = new Scanner(System.in).nextLine();//"aa bb cc dd"
        String[] a = s1.split("\\s+");  // \s空白字符,是一到多个

        for (String k :a){
            c.queueBind(queue,"direct_logs",k);
        }
        //接收消息
        DeliverCallback  deliverCallback = (consumerTag,message) ->{
            String s = new String(message.getBody());
            System.out.println("收到:"+s);
        };
        CancelCallback cancelCallback = consumerTag->{
        };
        c.basicConsume(queue,true,deliverCallback,cancelCallback);

    }

}

主题模式

绑定键有一个特别的格式,比如aa.bb.cc,或者用*.*.cc.dd或者aa.#,和路由模式非常的相近,使用的交换机是tocpic,只不过绑定键的格式发生了变化

然后代码和上面路由模式的代码没啥变化,交换机部分更改,有directtopic

举例(只修改交换机其他都一样)

c.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);

RPC模式

  • 对于RPC请求,客户端发送一条带有两个属性的消息:replyTo,设置为仅为请求创建的匿名独占队列,和correlationId,设置为每个请求的惟一id值。

  • 请求被发送到rpcqueue队列。

  • RPC工作进程(即:服务器)在队列上等待请求。当一个请求出现时,它执行任务,并使用replyTo字段中的队列将结果发回客户机。

  • 客户机在回应消息队列上等待数据。当消息出现时,它检查correlationId属性。如果匹配请求中的值,则向程序返回该响应数据。

  • 客户端

package m6;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;

public class RpcClient {
    static BlockingQueue<String> q =
            new ArrayBlockingQueue<>(10);

    public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
        while (true) {
            System.out.print("求第几个斐波那契数:");
            int n = new Scanner(System.in).nextInt();
            long r = f(n);
            System.out.println("结果: "+r);
        }
    }

    private static long f(int n) throws IOException, TimeoutException, InterruptedException {
        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.64.140");
        f.setPort(5672);
        f.setUsername("guest");
        f.setPassword("guest");
        Connection con = f.newConnection();
        Channel c = con.createChannel(); //通信的通道

        //创建调用队列和返回队列
        c.queueDeclare("rpcQueue",false,false,false,null);
        String replyTo = c.queueDeclare().getQueue();//由rabbitmq服务器自动命名,false,true, true
        //随机产生一个关联id
        String cid = UUID.randomUUID().toString();
        //发送调用,携带返回队列名和关联id
        AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
                .replyTo(replyTo).correlationId(cid).build();
        c.basicPublish("","rpcQueue",props,String.valueOf(n).getBytes());
        //不用等待结果,可以继续执行其他运算
        //直到需要结果时,再从rabbitmq接收结果,在新的线程中接收消息放入阻塞队列
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            q.add(new String(message.getBody()));
            String cid2 = message.getProperties().getCorrelationId();
            System.out.println(cid2);
        };
        CancelCallback cancelCallback = consumerTag -> {};
        c.basicConsume(replyTo,true, deliverCallback, cancelCallback);
        //主线程从阻塞队列获取结果数据
        String r = q.take();
        return Long.valueOf(r);
    }
}

  • 服务端

    package m6;
    
    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    import java.util.concurrent.TimeoutException;
    
    public class RpcServer {
        public static void main(String[] args)throws IOException, TimeoutException {
            //连接
            ConnectionFactory f = new ConnectionFactory();
            f.setHost("192.168.64.140");
            f.setPort(5672);
            f.setUsername("guest");
            f.setPassword("guest");
            Connection con = f.newConnection();
            Channel c = con.createChannel(); //通信的通道
    
            //接收调用数据: 1.n  2.返回队列名  3.关联id
            //计算第n个斐波那契数
            //向客户端发送结果
    
            c.queueDeclare("rpcQueue",false,false,false,null);
    
            DeliverCallback deliverCallback = (consumerTag, message) -> {
                Integer n = Integer.valueOf(new String(message.getBody()));  // n
                AMQP.BasicProperties props = message.getProperties();
                String replyTo = props.getReplyTo();    // 返回队列名
                String cid = props.getCorrelationId();  // 关联id
                //求第n个斐波那契数
                long r = fbnq(n);
    
                //发回结果
                //属性对象中添加关联id参数
                props =
                    new AMQP.BasicProperties.Builder()
                            .correlationId(cid).build();
                c.basicPublish("",replyTo,props,String.valueOf(r).getBytes());
            };
            CancelCallback cancelCallback = consumerTag -> {};
            c.basicConsume("rpcQueue",true,deliverCallback,cancelCallback);
    
        }
    
        /*
        求第n个斐波那契数
        1 1 2 3 5 8 13 21 34 55 89 144
         */
        static long fbnq(int n) {
            if (n < 1) {
                return 0;
            }
            if (n == 1 || n == 2) {
                return 1;
            }
            //递归求斐波那契数是低效算法,应该使用循环计算
            return fbnq(n-1) + fbnq(n-2);
        }
    }
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是呈祥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值