前言:本文为原创 若有错误欢迎评论!
一.基本介绍
1.安装
请参考后面博客的centos安装应用的博客!
2.客户端
-
打开 http://192.168.56.129:15672(默认账号密码 guest guest)
-
virtual host概念
是虚拟划分资源 就好比redis有16个数据库 只是逻辑划分 并不是平分资源
用于划分模块 每个virtual host下的exchange和quene不可以同名 -
创建一个新virtual host 并给guest添加该虚拟主机权限(也可以新创建一个用户 分配给新用户)
二.五种基本模型
1.准备
- 依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.6.6(和安装的rabbitmq版本一致)</version>
</dependency>
- 生产者和消费者都获要在最前面先获得channel(通道):
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.11.76");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");//没有设置账号和密码 因为默认是guest guest
Connection connection = connectionFactory.newConnection();
// 从连接中创建通道,这是完成大部分API的地方。
Channel channel = connection.createChannel();
- 生产者最后要关闭资源(消费者不关 要一直监听)
//关闭通道和连接
channel.close();
connection.close();
2.模型(默认交换机)
-
基本消费模型
(一个生产者、默认交换机、一个队列、一个消费者)producer:只声明一个队列 //队列名字 String queneName= "simple_queue" // 消息内容 String message = "Hello World!"; // 声明(创建)队列,必须声明队列才能够发送消息,我们可以把消息发送到队列中。 // 声明一个队列是幂等的 - 只有当它不存在时才会被创建 channel.queueDeclare(queneName, false, false, false, null); channel.basicPublish("",queneName, null, message.getBytes()); consumer:只声明一个队列 也不用任何绑定 / 声明队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 定义队列的消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { // body 即消息体 String msg = new String(body); System.out.println(" [x] received : " + msg + "!"); // 手动进行ACK channel.basicAck(envelope.getDeliveryTag(), false); } }; // 监听队列,第二个参数false,手动进行ACK channel.basicConsume(QUEUE_NAME, false, consumer); (ACK:即收到消息后要确认消息被消费才会在队列中删除该消息)
-
work消息模型
(一个生产者、默认交换机、一个队列、多个消费者)producer:不变 但是channel.basicPublish()多调用几次发送消息 consumer:增加:channel.basicQos(1);//一次可以消费多少个消息 且建立多个刚才的消费对象 还是不设置exchange且监听同一队列 (如果不在consumer设置channel.basicQos() 如果消息有堆积 则会均匀的分配给每个消费者 因为各消费性能不同 可能造成资源)
3.模型(指定交换机)
- 概念:
多个消费者 -> 每个消费者都有自己的队列 -> 每个队列绑定到交换机
生产者发送消息到交换机 -> 交换机决定发送到哪个队列)
-
订阅模型(fanout)
(和fanout交换机绑定的队列都可以消费该消息 即一个消息多个消费者消费)producer:声名exchange并发送到exchange 不再声明quene与不发到quene private final static String EXCHANGE_NAME = "fanout_exchange_test"; // 声明exchange,指定类型为fanout channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); // 消息内容 String message = "Hello everyone"; // 发布消息到Exchange channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); consumer(要复制多个):声明队列后还要声明交换机 只绑定到交换机 //声明队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 绑定队列到交换机 channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ""); // 定义队列的消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException { // body 即消息体 String msg = new String(body); System.out.println(" [消费者1] received : " + msg + "!"); } }; // 监听队列,自动返回完成 channel.basicConsume(QUEUE_NAME, true, consumer);
-
订阅模型(Direct)
(和direct交换机绑定的队列 只有routing对应才可以消费 即只能被一个队列消费)producer:声明交换机 在发送消息时指定routing private final static String EXCHANGE_NAME = "direct_exchange_test"; // 声明exchange,指定类型为direct channel.exchangeDeclare(EXCHANGE_NAME, "direct"); // 消息内容 String message = "商品新增了, id = 1001"; // 发送消息,并且指定routing key 为:insert ,代表新增商品 channel.basicPublish(EXCHANGE_NAME, "insert", null, message.getBytes()); consumer(要复制多个):要同时声明队列和交换机 然后队列绑定交换机时routing不同 可以绑定多次交换机 private final static String QUEUE_NAME = "direct_exchange_queue_1"; private final static String EXCHANGE_NAME = "direct_exchange_test"; channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 绑定队列到交换机,同时指定需要订阅的routing key。假设此处需要update和delete消息 channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "update"); channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "delete"); // 定义队列的消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用 @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException { // body 即消息体 String msg = new String(body); System.out.println(" [消费者1] received : " + msg + "!"); } }; // 监听队列,自动ACK channel.basicConsume(QUEUE_NAME, true, consumer);
注意:
- 一个channel连接可以声明多个不同名的quene和多个不同名exchange 且一个quene可以用不同的routing与exchange绑定多次 也可以和多个exchange进行绑定
- 但是每次声明一个队列就等于创建一个队列 绑定的结果可以修改 但是声明的时候的属性不可以不一样 不然会报错 此时应该调用修改的api
-
订阅模型(Topic)
(比"Drict"多了通配符而已)“*”:只匹配" . “后一个单词(如
audit.*
:只能匹配audit.irs
)
“#”:匹配不论” . "多少个单词(如audit.#
:能够匹配audit.irs.corporate
或者audit.irs
)producer:channel.basicPublish(EXCHANGE_NAME, "insert.test", null, message.getBytes()); consumer:channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "insert.#");
-
总结
- 前两个模型生产和消费都只声明一个quene
- fanout
生产者:声明exchange、发布时不用指定routing
消费者:声明exchange和quene 但quene绑定exchange时不指定routing - direct和topic
生产者:声明exchange、发布时指定routing
消费者:声明exchange和quene、quene绑定exchange时通过routing不同可以绑定多次
4.设置消息的属性(AMQP.BasicProperties)
producer:
Map<String, Object> headers = new HashMap<>();
headers.put("properties1", "111");
headers.put("properties2", "222");
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)//1:不持久化 2:持久化
.contentEncoding("UTF-8")
.expiration("10000")//过期时间 单位是毫秒
.headers(headers)//添加一些自定义属性 可以在消费端用map形式取出
.build();
channel.basicPublish("", "test001", properties, msg.getBytes());//既没有声明队列 也没有声明交换机就使用的是默认交换机
consumer:
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
Map<String, Object> propertiesHeaders = properties.getHeaders();
}
};
三.高级特性
1.confirm机制
-
概念:
如果消息发送到交换机不成功会返回失败的ack -
provider:
String exchangeName = "test_confirm_exchange"; String routingKey = "confirm.save"; String msg = "Hello RabbitMQ Send confirm message!"; //必须先指定消息投递模式: 确认模式 channel.confirmSelect(); //添加一个确认监听 channel.addConfirmListener(new ConfirmListener() { @Override public void handleNack(long deliveryTag, boolean multiple) throws IOException { System.err.println("-------no ack!-----------"); } @Override public void handleAck(long deliveryTag, boolean multiple) throws IOException { System.err.println("-------ack!-----------"); } channel.basicPublish(exchangeName, routingKey, null, msg.getBytes()); //有监听就不要在最后关闭资源
-
consumer:
DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { if(true) { channel.basicAck(envelope.getDeliveryTag(), false); }else { channel.basicNack(envelope.getDeliveryTag(),false,false); } } }; // 手工签收 必须要关闭 autoAck = false channel.basicConsume(queueName, false,consumer);
Envelope的属性
deliveryTag:消息的唯一标识 是判断是否返回ack成功的唯一标识
String _exchange:来自哪个交换机
String _routingKey:来自哪个路由void basicAck(long var1, boolean var3):
var1:消息的标识
var2:是否批量返回ack成功void basicNack(long var1, boolean var3, boolean var4) throws IOException;
var1:消息的标识
var3:是否批量返回ack失败
var4:是否加入队列尾再次重试
2.Return机制
-
概念:
如果消息从交换机路由队列不成功就会回调该方法 -
producer:
String exchange = "test_return_exchange"; String routingKeyError = "abc.save"; String msg = "Hello RabbitMQ Return Message"; //添加一个return回调的监听 channel.addReturnListener(new ReturnListener() { @Override public void handleReturn(int replyCode, String replyText, String exchange,String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException { } }); //必须指定第三个参数boolean mandatory为true 开启委托 channel.basicPublish(exchange, routingKeyError, true, null, msg.getBytes());
总结:
如果消息没有到exchange,则confirm回调,ack=false
如果消息到达exchange,则confirm回调,ack=true
exchange到queue成功,则不回调return
exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
3.消息限流
-
概念:
如果消息大量堆积不限流可能压垮服务器 且不限流会在堆积时均等分配 造成资源浪费 -
cousumer:
//限流方式 第一件事就是 autoAck设置为 false 只有返回ack消费成功才接着接收消息 channel.basicQos(0, 1, false); channel.basicConsume(queueName, false, new MyConsumer(channel));
void basicQos(int prefetchSize, int prefetchCount, boolean global)
prefetchSize:默认就设置为0
prefetchCount:一次处理的消息个数 通常都设置为1
global:是否在exchange级别进行限流 设为false则时consumer级别的限流
4.死信队列
-
原因
- 消息被拒绝 (如:当"basic.reject"、“basic.nack” 时requene=false)
- 消息在路由到队列之后TTL过期(TTL有两种:在客户端给队列的全部消息设置一个过期时间、在发送消息时给消息单独设置)
- 达到队列最大长度
-
概念:
如果消息变成死信 则会把消息投递到声明队列时设置的交换机上(routing在投递到死信的交换机时不变) -
consumer:
Map<String, Object> agruments = new HashMap<String, Object>(); agruments.put("x-dead-letter-exchange", "dlx.exchange"); agruments.put("x-dead-letter-routing-key", "dlx.routingkey"); //这个agruments属性,要设置到声明队列上 channel.queueDeclare(queueName, true, false, false, agruments); channel.exchangeDeclare(exchangeName, "topic", true, false, null); channel.queueBind(queueName, exchangeName, routingKey); channel.basicConsume(queueName, true, new MyConsumer(channel));
x-dead-letter-exchange:
是设置队列属性里的死信队列的名字(不可以改变 也可以通过这种方式设置队列其他属性)
x-dead-letter-routing-key:
设置死信在死信交换的路由(必须要设置 二者缺一不可)注意:必须提前把这个交换机及其接收对应消息的队列创建好才可以接收死信消息