RabbitMq六种工作模式

1.简单模式

RabbitMQ是一个消息中间件,你可以想象它是一个邮局。当你把信件放到邮箱里时,能够确信邮递员会正确地递送你的信件。RabbitMq就是一个邮箱、一个邮局和一个邮递员。

  • 发送消息的程序是生产者
  • 队列就代表一个邮箱。虽然消息会流经RbbitMQ和你的应用程序,但消息只能被存储在队列里。队列存储空间只受服务器内存和磁盘限制,它本质上是一个大的消息缓冲区。多个生产者可以向同一个队列发送消息,多个消费者也可以从同一个队列接收消息.
  • 消费者等待从队列接收消息
    在这里插入图片描述

pom.xml

添加slf4j依赖,和rabbitmq amqp依赖

<dependencies>
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>5.4.3</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.8.0-alpha2</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.8.0-alpha2</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.0</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>

生产者发送消息

public class Producer {
    public static void main(String[] args) throws Exception{
        //连接 rabbitmq服务器
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.66.129");
        f.setPort(5672);//收发消息是5672是通信端口
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");//设置虚拟主机

        Connection con = f.newConnection();
        Channel c = con.createChannel();

        //定义队列,会通知服务器想使用一个 "helloworld" 队列
        //服务器会找到这个队列,如果不存在,服务器会新建队列
        // 5个参数
        //      队列名,是否持久队列,是否排他(独占)队列,是否自动删除,其他参数属性
        c.queueDeclare("helloworld",false,false,false,null);

        //发送消息
        // 4个参数
        //      空串儿-后面用到时在介绍,队列名,其他参数属性,消息数据
        c.basicPublish("","helloworld",null,"Hello world".getBytes());
        System.out.println("消息已发送");
        c.close();
        con.close();
    }
}

消费者接受消息

public class Consumer {
    public static void main(String[] args) throws Exception{
        //连接
        ConnectionFactory f = new ConnectionFactory();
        f.setHost("192.168.66.129");
        f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");//设置虚拟主机
        Channel c = f.newConnection().createChannel();//新建(重用)连接,创建通道
        // 定义队列
        c.queueDeclare("helloworld",false,false,false,null);
        //创建处理消息的回调对象
        DeliverCallback deliverCallback = new DeliverCallback() {
            @Override
            public void handle(String consumerTag, Delivery message) throws IOException {
                byte[] body = message.getBody();
                String msg=new String(body);
                System.out.println("收到:"+msg);
            }

        };
        //创建取消接受的回调对象
        CancelCallback cancelCallback = new CancelCallback() {
            @Override
            public void handle(String consumerTag) throws IOException {

            }
        };
        //从helloworld 队列接受消息,消费消息
        c.basicConsume("helloworld",true,deliverCallback,cancelCallback);
    }
}

2.工作模式

RabbitMq六种工作模式_第1张图片
工作队列(即任务队列)背后的主要思想是避免立即执行资源密集型任务,并且必须等待它完成。相反,我们将任务安排在稍后完成。

我们将任务封装为消息并将其发送到队列。后台运行的工作进程将获取任务并最终执行任务。当运行多个消费者时,任务将在它们之间分发。

使用任务队列的一个优点是能够轻松地并行工作。如果我们正在积压工作任务,我们可以添加更多工作进程,这样就可以轻松扩展。

1.2.1生产者发送消息

public class Producer {
    public static void main(String[] args) throws Exception{
        ConnectionFactory f= new ConnectionFactory();
        f.setHost("192.168.66.129");
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");
        Channel c = f.newConnection().createChannel();
        //定义队列
        c.queueDeclare("task_queue",false,false,false,null);
        //发消息
        while (true){
            System.out.println("输入消息");
            String msg= new Scanner(System.in).nextLine();
            c.basicPublish("","task_queue", null,msg.getBytes());
        }
    }


}

1.2.2消费者接受消息

public class Consumer {
    public static void main(String[] args) throws Exception{
        //连接
        ConnectionFactory f= new ConnectionFactory();
        f.setHost("192.168.66.129");
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");
        Channel c = f.newConnection().createChannel();
        //定义队列
        c.queueDeclare("task_queue",false,false,false,null);
        //处理消息
        DeliverCallback deliverCallback = new DeliverCallback() {
            @Override
            public void handle(String consumerTag, Delivery message) throws IOException {
                String msg=new String(message.getBody());
                System.out.println("收到:"+msg);
                for (int i = 0; i <msg.length() ; i++) {
                    if(msg.charAt(i)=='.'){
                        try{
                            Thread.sleep(1000);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                }
                System.out.println("消息处理完");
            }
        };
        CancelCallback cancelCallback = new CancelCallback() {

            @Override
            public void handle(String consumerTag) throws IOException {

            }
        };
        //消费数据
        c.basicConsume("task_queue",true,deliverCallback,cancelCallback);
        System.out.println("开始消费数据");

    }
}

1.2.3运行测试

运行:
- 一个生产者
- 连个消费者
生产者发送多条消息
如: 1,2,3,4,5 两个消费者分别收到:

  • 消费者一: 1,3,5
  • 消费者二: 2,4
    rabbitmq在所有消费者中轮询分发消息,把消息均匀地发送给所有消费者

2.2.1消息确认

一个消费者接收消息后,在消息没有完全处理完时就挂掉了,那么这时会发生什么呢?

就现在的代码来说,rabbitmq把消息发送给消费者后,会立即删除消息,那么消费者挂掉后,它没来得及处理的消息就会丢失

如果生产者发送以下消息:
1…

2

3

4

5

两个消费者分别收到:

消费者一: 1…, 3, 5 消费者二: 2, 4
当消费者一收到所有消息后,要话费7秒时间来处理第一条消息,这期间如果关闭该消费者,那么1未处理完成,3,5则没有被处理

我们并不想丢失任何消息, 如果一个消费者挂掉,我们想把它的任务消息派发给其他消费者

为了确保消息不会丢失,rabbitmq支持消息确认(回执)。当一个消息被消费者接收到并且执行完成后,消费者会发送一个ack (acknowledgment) 给rabbitmq服务器, 告诉他我已经执行完成了,你可以把这条消息删除了。

如果一个消费者没有返回消息确认就挂掉了(信道关闭,连接关闭或者TCP链接丢失),rabbitmq就会明白,这个消息没有被处理完成,rebbitmq就会把这条消息重新放入队列,如果在这时有其他的消费者在线,那么rabbitmq就会迅速的把这条消息传递给其他的消费者,这样就确保了没有消息会丢失。

这里不存在消息超时, rabbitmq只在消费者挂掉时重新分派消息, 即使消费者花非常久的时间来处理消息也可以

手动消息确认默认是开启的,前面的例子我们通过autoAck=ture把它关闭了。我们现在要把它设置为false,然后工作进程处理完意向任务时,发送一个消息确认(回执)。

public class Consumer {
    public static void main(String[] args) throws Exception{
        //连接
        ConnectionFactory f= new ConnectionFactory();
        f.setHost("192.168.66.129");
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");
        Channel c = f.newConnection().createChannel();
        //定义队列
        c.queueDeclare("task_queue",false,false,false,null);
        //处理消息
        DeliverCallback deliverCallback = new DeliverCallback() {
            @Override
            public void handle(String consumerTag, Delivery message) throws IOException {
                String msg=new String(message.getBody());
                System.out.println("收到:"+msg);
                for (int i = 0; i <msg.length() ; i++) {
                    if(msg.charAt(i)=='.'){
                        try{
                            Thread.sleep(1000);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                }
                c.basicAck(message.getEnvelope().getDeliveryTag(),false);
                System.out.println("消息处理完");
            }
        };
        CancelCallback cancelCallback = new CancelCallback() {

            @Override
            public void handle(String consumerTag) throws IOException {

            }
        }; 
        //消费数据
        c.basicConsume("task_queue",false,deliverCallback,cancelCallback);
        System.out.println("开始消费数据");

    }
}

使用以上代码,就算杀掉一个正在处理消息的工作进程也不会丢失任何消息,工作进程挂掉之后,没有确认的消息就会被自动重新传递。

2.2.2合理的分发

rabbitmq会一次把多个消息分发给消费者, 这样可能造成有的消费者非常繁忙, 而其它消费者空闲. 而rabbitmq对此一无所知, 仍然会均匀的分发消息

我们可以使用 basicQos(1) 方法, 这告诉rabbitmq一次只向消费者发送一条消息, 在返回确认回执前, 不要向消费者发送新消息. 而是把消息发给下一个空闲的消费者
RabbitMq六种工作模式_第2张图片

		//QOS: Quality of Service
        //理解: 每次抓取的消息数量
        //如果设置成1,每次只抓取一条消息,这条消息处理完之前,不会继续抓取吓一条
        //必须在手动Ack模式下才有效
         c.basicQos(1);

2.2.3消息持久化

当rabbitmq关闭时, 我们队列中的消息仍然会丢失, 除非明确要求它不要丢失数据

要求rabbitmq不丢失数据要做如下两点: 把队列和消息都设置为可持久化(durable)

队列设置为可持久化, 可以在定义队列时指定参数durable为true

//第二个参数是持久化参数durable
ch.queueDeclare("某某", true, false, false, null);

由于之前我们已经定义过队列"某某"是不可持久化的, 对已存在的队列, rabbitmq不允许对其定义不同的参数, 否则会出错, 所以这里我们定义一个不同名字的队列"task_queue"

//定义一个新的队列,名为 task_queue
//第二个参数是持久化参数 durable
ch.queueDeclare("task_queue", true, false, false, null);

生产者和消费者代码都要修改

这样即使rabbitmq重新启动, 队列也不会丢失. 现在我们再设置队列中消息的持久化, 使用MessageProperties.PERSISTENT_TEXT_PLAIN参数

//第三个参数设置消息持久化
ch.basicPublish("", "task_queue",
            MessageProperties.PERSISTENT_TEXT_PLAIN,
            msg.getBytes());

下面是"工作模式"最终完成的生产者和消费者代码

生产者代码

public class Producer {
    public static void main(String[] args) throws Exception{
        ConnectionFactory f= new ConnectionFactory();
        f.setHost("192.168.66.129");
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");
        Channel c = f.newConnection().createChannel();
        //定义队列
        c.queueDeclare("task_queue",true,false,false,null);
        //发消息
        while (true){
            System.out.println("输入消息");
            String msg= new Scanner(System.in).nextLine();
            c.basicPublish("","task_queue", MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes());
        }
    }
}

消费者代码

public class Consumer {
    public static void main(String[] args) throws Exception{
        //连接
        ConnectionFactory f= new ConnectionFactory();
        f.setHost("192.168.66.129");
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");
        Channel c = f.newConnection().createChannel();
        //定义队列
        c.queueDeclare("task_queue",true,false,false,null);
        //处理消息
        DeliverCallback deliverCallback = new DeliverCallback() {
            @Override
            public void handle(String consumerTag, Delivery message) throws IOException {
                String msg=new String(message.getBody());
                System.out.println("收到:"+msg);
                for (int i = 0; i <msg.length() ; i++) {
                    if(msg.charAt(i)=='.'){
                        try{
                            Thread.sleep(1000);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                }
                c.basicAck(message.getEnvelope().getDeliveryTag(),false);
                System.out.println("消息处理完");
            }
        };
        CancelCallback cancelCallback = new CancelCallback() {

            @Override
            public void handle(String consumerTag) throws IOException {

            }
        };
        c.basicQos(1);
        //QOS: Quality of Service
        //理解: 每次抓取的消息数量
        //如果设置成1,每次只抓取一条消息,这条消息处理完之前,不会继续抓取吓一条
        //必须在手动Ack模式下才有效
        //消费数据
        c.basicConsume("task_queue",false,deliverCallback,cancelCallback);
        System.out.println("开始消费数据");

    }
}

3.发布和订阅模式

RabbitMq六种工作模式_第3张图片

RabbitMq六种工作模式_第4张图片
在前面的例子中,我们任务消息只交付给一个工作进程。在这部分,我们将做一些完全不同的事情——我们将向多个消费者传递同一条消息。这种模式称为“发布/订阅”。

为了说明该模式,我们将构建一个简单的日志系统。它将由两个程序组成——第一个程序将发出日志消息,第二个程序接收它们。

在我们的日志系统中,接收程序的每个运行副本都将获得消息。这样,我们就可以运行一个消费者并将日志保存到磁盘; 同时我们可以运行另一个消费者在屏幕上打印日志。

最终, 消息会被广播到所有消息接受者

3.1Exchanges交换机

RabbitMQ消息传递模型的核心思想是,生产者永远不会将任何消息直接发送到队列。实际上,通常生产者甚至不知道消息是否会被传递到任何队列。

相反,生产者只能向交换机(Exchange)发送消息。交换机是一个非常简单的东西。一边接收来自生产者的消息,另一边将消息推送到队列。交换器必须确切地知道如何处理它接收到的消息。它应该被添加到一个特定的队列中吗?它应该添加到多个队列中吗?或者它应该被丢弃。这些规则由exchange的类型定义。

有几种可用的交换类型:direct、topic、header和fanout。我们将关注最后一个——fanout。让我们创建一个这种类型的交换机,并称之为 logs: ch.exchangeDeclare(“logs”, “fanout”);

fanout交换机非常简单。它只是将接收到的所有消息广播给它所知道的所有队列。这正是我们的日志系统所需要的。

我们前面使用的队列具有特定的名称(还记得hello和task_queue吗?)能够为队列命名对我们来说至关重要——我们需要将工作进程指向同一个队列,在生产者和消费者之间共享队列。

但日志记录案例不是这种情况。我们想要接收所有的日志消息,而不仅仅是其中的一部分。我们还只对当前的最新消息感兴趣,而不是旧消息。

要解决这个问题,我们需要两件事。首先,每当我们连接到Rabbitmq时,我们需要一个新的空队列。为此,我们可以创建一个具有随机名称的队列,或者,更好的方法是让服务器为我们选择一个随机队列名称。其次,一旦断开与使用者的连接,队列就会自动删除。在Java客户端中,当我们不向queueDeclare()提供任何参数时,会创建一个具有生成名称的、非持久的、独占的、自动删除队列

//自动生成队列名
//非持久,独占,自动删除
String queueName = ch.queueDeclare().getQueue();

3.2绑定 Bindings

RabbitMq六种工作模式_第5张图片
我们已经创建了一个fanout交换机和一个队列。现在我们需要告诉exchange向指定队列发送消息。exchange和队列之间的关系称为绑定

//指定的队列,与指定的交换机关联起来
//成为绑定 -- binding
//第三个参数时 routingKey, 由于是fanout交换机, 这里忽略 routingKey
ch.queueBind(queueName, "logs", "");

现在, logs交换机将会向我们指定的队列添加消息

列出绑定关系:

rabbitmqctl list_bindings

3.3完整的代码

RabbitMq六种工作模式_第6张图片

生产者

生产者发出日志消息,看起来与前一教程没有太大不同。最重要的更改是,我们现在希望将消息发布到logs交换机,而不是无名的日志交换机。我们需要在发送时提供一个routingKey,但是对于fanout交换机类型,该值会被忽略。

public class Producer {
    public static void main(String[] args) throws Exception{
         //创建连接
        ConnectionFactory f= new ConnectionFactory();
        f.setHost("192.168.66.129");
        //f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");
        Channel c = f.newConnection().createChannel();

        //定义交换机
        c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
        //向交换机发送数据
        while (true){
            System.out.println("输入消息");
            String msg = new Scanner(System.in).nextLine();
            //四个参数
            // 交换机名,队列名(对于fanout交换机无效)
            c.basicPublish("logs","",null,msg.getBytes());
        }
    }
}

消费者

如果还没有队列绑定到交换器,消息就会丢失,但这对我们来说没有问题;如果还没有消费者在听,我们可以安全地丢弃这些信息。

public class Consumer {
    public static void main(String[] args) throws Exception{
        //创建连接
        ConnectionFactory f= new ConnectionFactory();
        f.setHost("192.168.66.129");
        //f.setPort(5672);
        f.setUsername("admin");
        f.setPassword("admin");
        f.setVirtualHost("/jyl");
        Channel c = f.newConnection().createChannel();

        //先做三步: 1.定义交换机  2.定义随机队列  3.绑定到交换机
        c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
        //String queue=UUID.randomUUID().toString();
        //队列名称,是否持久化,独占,自动删除,其他参数属性
        //c.queueDeclare(queue,false,true,true,null);
        //让rabbitmq服务器来随机命名,非持久,独占,自动删除
        String queue = c.queueDeclare().getQueue();
        //第三个参数对 fanout交换机无效
        c.queueBind(queue,"logs","");
        DeliverCallback  deliverCallback=new DeliverCallback() {
            @Override
            public void handle(String consumerTag, Delivery message) throws IOException {
                String msg = new String(message.getBody());
                System.out.println("收到:" + msg);
            }
        };
        CancelCallback cancelCallback = new CancelCallback() {
            @Override
            public void handle(String consumerTag) throws IOException {

            }
        };
        //正常消费数据
        c.basicConsume(queue,true, deliverCallback, cancelCallback);
    }
}

你可能感兴趣的:(RabbitMq)