安装rabbitmq见上一篇写的文章。
rabbitmq服务是随Windows一起启动的,和mysql一样,所以只要安装了即可。
消息队列有两种模式
生产者-消费者:生产者发一个消息到队列里,只有一个消费者能获得,谁先到谁先得。
发布者-订阅者:生产者发一个消息到队列里,所有消费者都能获得这个消息
需要的jar包:rabbitmq-client.jar
MqSender.java 生产者、发布者 角色
package test;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* 主要流程:
* (1):创建ConnectionFactory,并且设置一些参数,比如hostname,portNumber等等
* (2):利用ConnectionFactory创建一个Connection连接
* (3):利用Connection创建一个Channel通道
* (4):创建queue并且和Channel进行绑定
* (5):创建消息,并且发送到队列中
* (6):关闭连接和通道
*
* @author yulisao
* @createDate 2019-9-11
* @description
*/
public class MqSender {
// 队列名称
private final static String QUEUE_NAME = "MyQueue1";
//交换机名称
private final static String EXCHANGE_NAME = "MyExchangeFanout";
private static ConnectionFactory factory;
private static Connection connection;
private static Channel channel;
public static void main(String[] args)
throws IOException, TimeoutException {
System.out.println("当前队列名:"+QUEUE_NAME);
System.out.println("请输入需要发送的消息内容");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
//consumeSend(scanner.next()); // 生产者
readSend(scanner.next()); // 发布者
}
}
/**
* 连接初始化
*
* @author yulisao
* @createDate 2019-9-11
* @description
* @throws IOException
*/
public static void init() throws IOException {
factory = new ConnectionFactory(); // 创建工厂
factory.setHost("localhost"); // 设置IP.端口,账户和密码,客户端访问需要的
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
connection = factory.newConnection();// 创建连接
channel = connection.createChannel();// 创建一个通道
}
/**
* 消费者没模式
*
* @author yulisao
* @createDate 2019-9-11
* @description
* @throws IOException
*/
public static void consumeSend(String messageText) throws IOException, TimeoutException {
init(); // 初始化
/* 消费者模式
* 声明(创建)队列
* 参数1:队列名称
* 参数2:为true时server重启队列不会消失
* 参数3:队列是否是独占的,如果为true只能被一个connection使用,其他连接建立时会抛出异常
* 参数4:队列不再使用时是否自动删除(没有连接,并且没有未处理的消息)
* 参数5:建立队列时的其他参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = messageText; // 消息主题内容
/*
* 向server发布一条消息
* 参数1:交换机exchange的名称,若为空则使用默认的exchange
* 参数2:队列映射的路由key
* 参数3:其他的属性
* 参数4:消息体
* RabbitMQ默认有一个exchange,叫default exchange,它用一个空字符串表示,它是direct exchange类型,
* 任何发往这个exchange的消息都会被路由到routing key的名字对应的队列上,如果没有对应的队列,则消息会被丢弃
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
System.out.println("[生产者][消费模式]已经发送消息:" + message);
// 关闭资源
channel.close();
connection.close();
}
/**
* 订阅者模式
*
* @author yulisao
* @createDate 2019-9-11
* @description
* @throws IOException
*/
public static void readSend(String messageText) throws IOException {
init(); // 初始化
/* 订阅者模式
* 声明exchange(交换机)
* 参数1:交换机名称
* 参数2:交换机类型 : direct 、fanout 、topic
* direct:
* fanout: 下发到所有绑在该交换机下的队列
* topic:
* 参数3:交换机持久性,如果为true则服务器重启时不会丢失
* 参数4:交换机在不被使用时是否删除
* 参数5:交换机的其他属性
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout",true,true,null);
String message = messageText; // 消息主题内容
// 第一个和第二个参数 位置相反。 第二个参数字符串B是路由key名称,在订阅者中与绑定队列channel.queueBind的第三个参数需保持一致,不然订阅者接收不到
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
System.out.println("[生产者][订阅模式]已经发送消息:" + message);
// 关闭资源
channel.close();
connection.close();
}
}
main是主方法, init是创建连接的。consumeSend是消费者模式, readSend是发布者模式,需要测试哪种模式就放开main里面的哪种调用。
MqReceiver.java 消费者、订阅者如下
package test;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
/**
* 主要流程:
* (1):创建ConnectionFactory
* (2):创建一个Connection连接
* (3):创建一个Channel通道
* (4):将queue和Channel进行绑定,注意这里的queue名字要和前面producer创建的queue一致
* (5):创建消费者Consumer来接收消息,同时将消费者和queue进行绑定
*
* @author yulisao
* @createDate 2019-9-11
* @description
*/
public class MqReceiver {
// 队列名称。消费者模式下各个消费端的队列名称要一致,订阅者模式下各个订阅者端不要相同
private final static String QUEUE_NAME = "MyQueue";
//交换机名称
private final static String EXCHANGE_NAME = "MyExchangeFanout";
private static ConnectionFactory factory;
private static Connection connection;
private static Channel channel;
public static void main(String[] args) throws IOException,
TimeoutException, ShutdownSignalException,
ConsumerCancelledException, InterruptedException {
System.out.println("当前队列名:"+QUEUE_NAME);
System.out.println("exeing.... ");
//consumeReceive(); // 消费者
readReceive(); // 订阅者
}
/**
* 连接初始化
*
* @author yulisao
* @createDate 2019-9-11
* @description
* @throws IOException
*/
public static void init() throws IOException {
factory = new ConnectionFactory();
factory.setHost("localhost");
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
}
/**
* 消费者模式
*
* @author yulisao
* @createDate 2019-9-11
* @description
* @throws IOException
*/
public static void consumeReceive() throws IOException, TimeoutException,
ShutdownSignalException, ConsumerCancelledException,
InterruptedException {
init(); // 初始化
/*
* 这行表示同一时刻服务器只会发一条消息给消费者
* 注释表示平均轮流模式:N个消费者,从第1到N个消费者轮流接一个消息,如此往复循环
* 放开表示能者多劳模式:N个消费者,谁处理的快,效率高,谁接的消息会多一些
*/
// channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
/*
* 监听队列
* 参数1: 队列名称
* 参数2: 是否自动回复。 true:自动 false:手动
* 参数3:消费者
*/
channel.basicConsume(QUEUE_NAME, true, consumer);
// 获取消息
while (true) { // 这个循环能保证一直在监听,不然执行一次整个main程序就执行完毕了。
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("[消费者]接收到消息是:" + message);
// 当channel.basicConsume里面第二个参数是false时,需要下面这行手动回复
// channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
// channel.basicReject(); channel.basicNack();
//可以通过这两个函数拒绝消息,可以指定消息在服务器删除还是继续投递给其他消费者
}
}
/**
* 订阅者模式
*
* @author yulisao
* @createDate 2019-9-11
* @description
* @throws IOException
* @throws InterruptedException
* @throws ConsumerCancelledException
* @throws ShutdownSignalException
*/
public static void readReceive() throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
init(); // 初始化
/*
* 绑定队列到交换机(交换机名称一定要和生产者交换机名称相同)
* 参数1:队列的名称
* 参数2:交换机的名称
* 参数3:Routing Key , 需与发布者的 channel.basicPublish 第二个参数一致 才能接收到消息
*
*/
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "A");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "B");
channel.basicQos(1); //同一时刻服务器只会发一条消息给订阅者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.err.println("[订阅者]接收到消息是:" + message);
// 手动回复
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
将MqReceiver打jar运行测试。
消费者模式下:
生产者发了7个消息出去。当前队列名是MyQueue ,消费者的队列名也必须都是MyQueue,不然接收不到消息
打包好消费者后,同时运行了三个,也就是三个消费者,三个消费者的代码都是一样的。理论上他们三个是轮流来一人一次的接收一次消息,如下图。
但如果加了channel.basicQos(1); 这行代码,则是能者多劳模式,谁处理的快谁就可以去优先竞争下一个消息。没加channel.basicQos(1); 这行就是轮流平均模式,这行表示同一时刻服务器只会发一条消息给消费者。 当然轮流平均只是理论上(比如消费者收到了消息但回复生产者的时候没成功或者网络不畅,生产者则会以为他没收到从而把这个消息另给一个消费者)。
redis里面的list类型的左添加lpush和右取出rpop也是一种队列机制,但是没有回复的功能,而rabbitmq则有自动回复和手动回复的功能优点在此,对队列安全严格重视的用rabbitmq比较好,不是很重视用redis也成,轻量级。
订阅者模式:
发布者和订阅者的交换机名称MyExchangeFanout必须一致,队列名字不用相同随便取名字,只要将取好的队列名绑到交换机MyExchangeFanout上即可收到发布者推送的全部消息。
运行三个订阅者,他们的队列名分别是queue1,queue2,queue3,服务的发布的消息他们三者都可以收到无需竞争。