普通模式
一个生产者,一个交换机,一个队列,一个消费者.
生产者
public class Send {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
// 初始化
ConnectionFactory factory = new ConnectionFactory();
// 这里需要设置主机名,端口,密码等,这里使用的工厂默认的配置
Connection connection = factory.newConnection();// 获取连接
Channel channel = connection.createChannel();// 获取通道
//声明队列
/** 队列声明的参数
* @param queue the name of the queue
* @param durable true if we are declaring a durable queue (the queue will survive a server restart)
* @param exclusive true if we are declaring an exclusive queue (restricted to this connection)
* @param autoDelete true if we are declaring an autodelete queue (server will delete it when no longer in use)
* @param arguments other properties (construction arguments) for the queue
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//定义消息
String message = "hello world";
// 发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
// 断开连接
channel.close();
connection.close();
}
}
消费者:
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//使用的是工厂的默认参数(主机名,端口,密码.....)
Connection conection = factory.newConnection();
Channel channel = conection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//创建消费者,在回调函数中处理结果
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println("从队列"+QUEUE_NAME+"接受到:" + message);
}
};
/**
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* acknowledged once delivered; false if the server should expect
* explicit acknowledgements.
* 是否需要自动签收消息,如果是true的话,一旦接收到消息,就返回确认签收,不会管回调
* 函数的执行. fasle的话为手动签收,等到回调函数逻辑处理完,在手动返回确认签收.
* 自动确认签收,可能会造成消息丢失.
* @param callback an interface to the consumer object
*/
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
自动确认签收(ack),消息丢失问题.
上面函数 basicConsume(),的autoAck 设置为true的话,是一旦接收到消息,就返回确认签收,如果回调函数出现异常,消息也会从队列中消失,但是这个消息(指令)并没有被执行,这就造成了消息丢失.
解决办法: 将签收方式(autoAck)改为false,手动签收.在回调函数中,用通道(channel)对象调用下面的方法,返回签收(ack).
/**
* Acknowledge one or several received
* messages. Supply the deliveryTag from the {@link com.rabbitmq.client.AMQP.Basic.GetOk}
* or {@link com.rabbitmq.client.AMQP.Basic.Deliver} method
* containing the received message being acknowledged.
* @see com.rabbitmq.client.AMQP.Basic.Ack
* @param deliveryTag the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
* @param multiple true to acknowledge all messages up to and
* including the supplied delivery tag; false to acknowledge just
* the supplied delivery tag.
* @throws java.io.IOException if an error is encountered
*/
void basicAck(long deliveryTag, boolean multiple) throws IOException;
work queue 模式
一个生产者,多个消费者,一个消息只会被分发到一个消费者.
work queue 模式在代码上没有任何区别,只不过多了一个(或多个)消费端.
生产者通过通道,声明队列,发送消息的时候,指定交换机.
消费者通过通道声明队列,通过通道指定队列名,拿消息.
work queue 问题
因为work queue由多个消费端,把消息发送给谁就是一个问题. RabbitMQ在此模式下,默认使用的是轮询方式. 但这就会带来一个问题, 不同的微服务性能不同, 性能差的在轮询模式下,可能会堆积很多的消息, 所以需要设置 一个消费者, 在同一时间只处理一个消息.
在channel对象 中设置
channel.basicQos(1); //同一时间只处理一个消息
以下是该方法源码
/**
* Request a specific prefetchCount "quality of service" settings
* for this channel.
*
* @see #basicQos(int, int, boolean)
* @param prefetchCount maximum number of messages that the server
* will deliver, 0 if unlimited
* @throws java.io.IOException if an error is encountered
*/
void basicQos(int prefetchCount) throws IOException;
发布/订阅模式(生产者声明交换机,消费者声明队列,绑定到生产者声明的交换机)
前两种模式,共用的一个队列,一个消息只能被一个消费者消费, 发布/订阅模式就是有多个队列,可以同时向多个队列发布消息,被多个消费者消费.
交换机 fanout模式
生产者通过channel声明交换机(只做声明交换机)
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
// 使用默认的工厂配置,通过连接工厂拿到连接对象
Connection connection = connectionFactory.newConnection();
// 通过连接对象拿到通道
Channel channel = connection.createChannel();
// 通过通道声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 通过通道发布消息
// rountingKey 为null
channel.basicPublish(EXCHANGE_NAME, "", null, "55开牛逼".getBytes("utf-8"));
// 关闭连接对象
channel.close();
connection.close();
}
消费者声明队列,将队列绑定到交换机,rountingKey为nul
消费者1:
public class Receive2 {
public static final String QUEUE_NAME = "receivor_2";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 将队列绑定到交换机,fanout模式不需要指定routingKey
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "");
Consumer callback = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag" + consumerTag);
System.out.println("接收到的消息" + new String(body, StandardCharsets.UTF_8));
}
};
// 消费消息
channel.basicConsume(QUEUE_NAME, true,callback);
}
}
消费者2:
代码与消费者1相同,只不过队列名不同, 这样就是两个队列绑定到一个交换机, 消息就会同时发布到两个队列,两个消费者就会同时接收到.
如果两个两个消费者绑定到同一个队列,就是work queue模式了…
交换机 Direct模式
direct模式和fanout模式差不多, direct模式不在把routingKey设置为 “” ,生产者把消息用通道给到指定的交换机, 并指定routingKey, 只有拥有指定的routingKey的消费者才会接收到消息.
将fanout 模式更改一下
生产者:
// 通过通道声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 通过通道发布消息,发布到routingKey为news的队列
// 这里的交换机名字要改变一下, 因为再次名字下的交换机已经再fanout模式下声明,直接变到Direct会报错.
channel.basicPublish(EXCHANGE_NAME, "news", null, "55开牛逼".getBytes("utf-8"));
消费者一:
// 将队列绑定到交换机,设置routingKey为 news,可以绑定多个routingkey
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "news");
消费者二:
// 将队列绑定到交换机,设置routingKey为 news,可以绑定多个routingkey
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "music");
消息只会发布到绑定到交换机routingKey为news的队列.
交换机topic模式
topic 模式是Direct的升级版本,生产者发布和消费者绑定的routingKey可以使用通配符.
‘#’ : 井号代表多个或者一个单词.
‘*’: ✳代表是一个单词.
修改一下Direct模式的代码:
生产者:
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 通过通道发布消息,EXCHANGE_NAME的值需要哦改一下
channel.basicPublish(EXCHANGE_NAME, "system.music", null, "55开牛逼".getBytes("utf-8"));
消费者一:
// 将队列绑定到交换机,routingKey为system.*
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "system.*");
消费者二:
// 将队列绑定到交换机,routingKey为system.music
channel.queueBind(QUEUE_NAME, Send.EXCHANGE_NAME, "system.music");
两个消费者都会收到生产者的消息.
持久化
交换机,队列,消息,都可以做持久化, 只需要在声明交换机/队列时将durable设置为true就可以了.
消息持久化
总结
在普通模式和work queue 模式 生产者通过channel声明队列, 通过默认的交换机发布消息
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//定义消息
String message = "hello world";
// 发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
消费者端也声明相同的队列,
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, true, consumer);
在订阅发布模式,生产者只声明交换机, 消费者只声明队列, 把队列绑定到交换机.