rabittmq初级教程

1.前言

2.RabbitMQ-Plugins插件安装

3.简单队列

4.work queues 工作队列 轮询分发

5.公平分发

6.发布订阅模式

7.路由模式

8.Topic exchange

9.rabbitMQ的消息确认机制(事物+confirm)

10.Spring集合rabbitmq

 

1.前言

1.消息列队解决了什么问题??

异步处理

应用解耦

流量削峰(高并发:抢红包、秒杀)

日志处理……

 

2.java操作RabbitMQ

  1. simple 简单队列
  2. work queues 工作队列
  3. publish/subscribe 发布订阅
  4. routing 路由选择 通配符模式
  5. topic 主题
  6. 手动和自动确认消息
  7. 队列的持久化和非持久化
  8. RabbitMQ的延迟队列

3.Spring AMQP协议

 

 

2.RabbitMQ-Plugins插件安装

打开

进⼊入rabbitmq的

sbin⽬目录(我的⽬目录是:C:\Program Files\RabbitMQ

Server\rabbitmq_server-3.7.2\sbin),输⼊入:

rabbitmq-plugins enable rabbitmq_management

命令,稍等会会发现出现plugins安装成功的提示

 

打开浏览器进入 http://localhost:15672/ 用户名guest 密码guest

 

  • 添加用户:(huyanglin huyanglin )

rabittmq初级教程_第1张图片

 

  • virtual hosts 管理

virtual hosts 相当于mysql 的db

 

rabittmq初级教程_第2张图片

一般以/开头

 

我们需要对用户进行授权

rabittmq初级教程_第3张图片

 

 
  
	com.rabbitmq
	amqp-client
	3.4.1
  
 
  
	org.slf4j
	slf4j-log4j12
	1.7.5
  
 
  
	log4j
	log4j
	1.2.17
  
 
  
	junit
	junit
	4.11
  
  

 

 

 

3.简单队列

P:消息的生产者-->队列-->消费者

连接rabbitmq

  public static Connection getConnections() throws IOException{
	  //定义一个连接工厂
	  ConnectionFactory factory=new ConnectionFactory();
	  //设置服务器ַ
	  factory.setHost("127.0.0.1");
      //	  AMQP  5672
	  factory.setPort(5672);
	  //vhost
	  factory.setVirtualHost("/vhost_mmr");
	  //用户名
	  factory.setUsername("huyanglin");
	  //密码
	  factory.setPassword("huyanglin");
	  Connection newConnection = factory.newConnection();
	  return newConnection;
  }

 

发送消息

  public static void main(String[] args) throws IOException {
	//1.获取链接
	  Connection connections = ConnectionUtils.getConnections();
	//2.创建通道
	  Channel channel = connections.createChannel();
	  channel.queueDeclare(QUEUE_NAME, false, false, false, null);
	  String msg="hello simple";
	  channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
	  System.out.println(msg);
	  channel.close();
	  connections.close();
}

 

监听(接收消息)

private static final String QUEUE_NAME="test_simple_queue";
public static void main(String[] args) throws IOException {
		//建立链接、创建通道
		Connection connections = ConnectionUtils.getConnections();
		Channel channel = connections.createChannel();
		//队列声明
		channel.queueDeclare(QUEUE_NAME, false, false, false, null)	;
		//定义消费者
		DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
			//获取到达的消息
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg=new String(body,"utf-8");
				System.out.println("取数据"+msg);
			};
		};
		//消费队列
	channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
	}

 

简单队列的不足

  1. 耦合性高,生产者一一对应消费者(不能满足多个消费者消费队列中的消息)
  2. 队列名变更,得同时变更

 

4.work queues 工作队列 轮询分发

为什么会出现工作队列

simple队列 是一一对应的,实际开发中,生产者发送消息是毫不费力的,而消费者一般是与业务结合的,消费者接收到消息后就需要处理,可能需要花费时间。这时候队列就会挤压着很多消息。

 

生产者:

public class Send {
	private static final String QUEUE_NAME = "test_work_queue";
	public static void main(String[] args) throws IOException, InterruptedException {
		// 获取链接
		Connection connections = ConnectionUtils.getConnections();
		// 获取channel
		Channel channel = connections.createChannel();
		//
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		for (int i = 0; i < 50; i++) {
			String msg = "workQueue" + i;
			channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
			System.out.println("发送的消息" + msg);
			Thread.sleep(i + 20);
		}
		channel.close();
		connections.close();
	}
}

 

消费者1

public class Recv1 {
	private static final String QUEUE_NAME = "test_work_queue";
 
	public static void main(String[] args) throws Exception {
		// 获取链接
		Connection connections = ConnectionUtils.getConnections();
		// 获取通道
		Channel channel = connections.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 msg = new String(body, "utf-8");
				System.out.println("消费者1" + msg);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					System.out.println("[1]  done");
				}
			}
		};
		boolean autoAck = true;
		channel.basicConsume(QUEUE_NAME, autoAck, consumer);
	}
}

 

消费者2

public class Recv2 {
	private static final String QUEUE_NAME = "test_work_queue";
 
	public static void main(String[] args) throws Exception {
		// 获取链接
		Connection connections = ConnectionUtils.getConnections();
		// 获取通道
		Channel channel = connections.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 msg = new String(body, "utf-8");
				System.out.println("消费者2" + msg);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					System.out.println("[2]  done");
				}
			}
		};
		boolean autoAck = true;
		channel.basicConsume(QUEUE_NAME, autoAck, consumer);
	}
}

 

现象:消费者1和消费者2处理的数据消息是一样的,

消费者1都是偶数

消费者2都是奇数

这种方式叫 轮询分发(round-robin)结果是 不管谁忙或者谁闲,任务消息都是一边一个轮询发

 

 

5.公平分发

生产者:在消费者返回确认消息之前,只分发一个消息

		/**
		 * 每个消费者发送确认消息之前,消息队列不发送下一个消息,一次只处理一个消息
		 */
		int prefecthCount=1;
		channel.basicQos(prefecthCount);

 

消费者 1.确认每次只收到一个消息

2.每次处理完消息要返回确认信息

3.自动应答 关闭

rabittmq初级教程_第4张图片

现象:消费者1处理得比消费者2多(能者多劳)

 

  • 消息应答 与 消息持久化

boolean autoAck = false;//自动应答=false

channel.basicConsume(QUEUE_NAME, autoAck, consumer);

 

boolean autoAck = true;

自动确认模式,一旦rabbitmq将消息分发给消费者,就会从内存中删除

这种情况,如果杀死正在执行任务的消费者,则会丢失正在处理的消息

 

boolean autoAck = false;

手动模式 ,如果有一个消费者挂掉,就会交付给其他消费者。rabbitMQ支出消息应答,消费者处理完消息后,给abbitmq发送确认消息,rabbitmq收到后就会删除内存中的消息

 

消息应答默认是打开的==>false

 

消息的持久化

boolean durable=false;//持久化

channel.queueDeclare(QUEUE_NAME, durable, false, false, null);

 

我们将程序中的 boolean durable=false 直接改成true是不可以的,因为test_work_queue队列已经定义成未持久化的队列,rabbitmq不允许重新定义已经存在的队列。

 

 

6.发布订阅模式

rabittmq初级教程_第5张图片

X:交换机、转发器

解读:1.一个生产者,多个消费者

2.每一个消费者都有自己的队列

3.生产者没有直接将消息发送到队列。而是发送到了交换机

4.每个队列都要绑定到交换机上

5.生产者发送的消息,经过交换机,到达队列,就能实现一个消息被多个消费者消费

生产者:

public class Send {
	private static final String EXCHANGE_NAME = "test_exchange_fanout";
 
	public static void main(String[] args) throws IOException {
		Connection connections = ConnectionUtils.getConnections();
		Channel channel = connections.createChannel();
		// 声明交换机
		channel.exchangeDeclare(EXCHANGE_NAME, "fanout");// 分发
 
		String msg = "hello ps";
		channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
		System.out.println("send  " + msg);
		channel.close();
		connections.close();
 
	}
}

 

rabittmq初级教程_第6张图片

这时候消息哪去了??

消息丢失了!因为交换机没有存储能力,在rabbitMQ里面,只有队列有存储能力

 

消费者1:

public class Recv1 {
	private static final String EXCHANGE_NAME = "test_exchange_fanout";
 
	private static final String QUEUE_NAME = "email_queue";
 
	public static void main(String[] args) throws IOException {
		Connection connections = ConnectionUtils.getConnections();
		final Channel channel = connections.createChannel();
		// 声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
		channel.basicQos(1);
		DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg = new String(body, "utf-8");
				System.out.println("recv11   "+msg);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					channel.basicAck(envelope.getDeliveryTag(),false);
				}
			}
		};
		channel.basicConsume(QUEUE_NAME, false,defaultConsumer);
	}
}

 

消费者2

 

public class Recv2 {
	private static final String EXCHANGE_NAME = "test_exchange_fanout";
 
	private static final String QUEUE_NAME = "massage_queue";
 
	public static void main(String[] args) throws IOException {
		Connection connections = ConnectionUtils.getConnections();
		final Channel channel = connections.createChannel();
		// 声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
		channel.basicQos(1);
		DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg = new String(body, "utf-8");
				System.out.println("recv22   "+msg);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					channel.basicAck(envelope.getDeliveryTag(),false);
				}
			}
		};
		channel.basicConsume(QUEUE_NAME, false,defaultConsumer);
	}
}

 

rabittmq初级教程_第7张图片

 

Exchange(交换机 转发器)

一方面是接收生产者的消息,一方面将消息推送到各个消费者的队列

Fanout(不处理路由键)

rabittmq初级教程_第8张图片

Direct (处理路由键)

rabittmq初级教程_第9张图片

7.路由模式

模型:

 

rabittmq初级教程_第10张图片

生产者:

public class Send {
	private static final String EXCHANGE_NAME = "test_exchange_direct";
 
	public static void main(String[] args) throws IOException {
 
		Connection connections = ConnectionUtils.getConnections();
		Channel channel = connections.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, "direct");
		String msg = new String("routing  test");
		String routingKey="info";
		System.out.println("send   " + msg);
		channel.basicPublish(EXCHANGE_NAME,routingKey, null, msg.getBytes());
		channel.close();      
		connections.close();
 
	}
}

消费者1:

public class Recv1 {
	private static final String QUEUE_NAME="erro_info_manage";
	private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException {
	Connection connections = ConnectionUtils.getConnections();
	final Channel channel = connections.createChannel();
	channel.queueDeclare(QUEUE_NAME, false, false, false, null);
	channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
	channel.basicQos(1);
	DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
		@Override
		public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
				throws IOException {
			String msg=new String(body,"utf-8");
			System.out.println("recv1"+msg);
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally{
				channel.basicAck(envelope.getDeliveryTag(), false);
			}
		}
	};
	channel.basicConsume(QUEUE_NAME,false, defaultConsumer);
 
}
}

消费者2:

public class Recv2 {
	private static final String QUEUE_NAME="all_info_manage";
	private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException {
	Connection connections = ConnectionUtils.getConnections();
	final Channel channel = connections.createChannel();
	channel.queueDeclare(QUEUE_NAME, false, false, false, null);
	channel.basicQos(1);
	channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
	channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "info");
	channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "warning");
	DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
		@Override
		public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
				throws IOException {
			String msg=new String(body,"utf-8");
			System.out.println("recv2   "+msg);
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally{
				channel.basicAck(envelope.getDeliveryTag(), false);
			}
		}
	};
	channel.basicConsume(QUEUE_NAME,false, defaultConsumer);
 
}
}

 

8.Topic exchange

将路由与某模式进行匹配

#-------匹配一个或者多个

*--------匹配一个

rabittmq初级教程_第11张图片

 

rabittmq初级教程_第12张图片

 

生产者:

public class Send {
	private static final String EXCHANGE_NAME = "test_exchange_topic";
 
	public static void main(String[] args) throws IOException {
 
		Connection connections = ConnectionUtils.getConnections();
		Channel channel = connections.createChannel();
		channel.exchangeDeclare(EXCHANGE_NAME, "topic");
		String msg = new String("商品-----");
		String routingKey="goods.del";
		System.out.println("send ---  " + msg);
		channel.basicPublish(EXCHANGE_NAME,routingKey, null, msg.getBytes());
		channel.close();      
		connections.close();
 
	}
}

 

消费者1:

public class Recv1 {
	private static final String QUEUE_NAME = "queue_topic_1";
	private static final String EXCHANGE_NAME = "test_exchange_topic";
 
	public static void main(String[] args) throws IOException {
		Connection connections = ConnectionUtils.getConnections();
		final Channel channel = connections.createChannel();
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "goods.add");
		channel.basicQos(1);
		DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg = new String(body, "utf-8");
				System.out.println("recv1   " + msg);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			}
		};
		channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
 
	}
}

 

消费者2:

public class Recv2 {
	private static final String QUEUE_NAME = "queue_topic_2";
	private static final String EXCHANGE_NAME = "test_exchange_topic";
 
	public static void main(String[] args) throws IOException {
		Connection connections = ConnectionUtils.getConnections();
		final Channel channel = connections.createChannel();
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "goods.*");
		channel.basicQos(1);
		DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				String msg = new String(body, "utf-8");
				System.out.println("recv2   " + msg);
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					channel.basicAck(envelope.getDeliveryTag(), false);
				}
			}
		};
		channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
 
	}
}

 

9.rabbitMQ的消息确认机制(事物+confirm)

在rabbitmq中,我们可以通过持久化数据,解决rabbitmq的服务器异常 的数据丢失问题

问题:生产者将消息发送出去后,是否到达rabbitmq服务器?默认的情况下是不知道的

 

两种方式解决:

1.AMQP 实现了事物机制

2.confirm 模式

 

AMQP 事物机制

txSelect 用户将当前channel设置成transation模式

txCommit 用于提交事物

txRollback 回滚事物

rabittmq初级教程_第13张图片

 

这种模式比较耗时,降低了rabbitmq的吞吐量

 

confirm模式

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理;

 

confirm模式最大的好处是 异步

rabbitmq如果服务器异常或者崩溃,就会发送一个nack消息

 

开启confirm模式

channel.confirmSelect();

 

编程模式

1.普通 发一条 waitForConfirm()

2.批量 发一批 waitForConfirms()

3.异步confirm模式 提供一个回调方法

 

confirm单条

rabittmq初级教程_第14张图片

 

confirm多条

public class SendMany {
	private static final String QUEUE_NAME = "confirm_test_1";
 
	public static void main(String[] args) throws IOException, InterruptedException {
		Connection connections = ConnectionUtils.getConnections();
		Channel channel = connections.createChannel();
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		// 生产者调用confirmSelect 将channel设置为confirm模式
		channel.confirmSelect();
		String msg = "hello  confirm  msg";
		System.out.println("send--confirm---" + msg);
		for (int i = 0; i < 10; i++) {
			channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
		}
		if (!channel.waitForConfirms()) {
			System.out.println("massage send failed");
		} else {
			System.out.println("massage send ok");
		}
		connections.close();
	}
}

 

异步confirm模式

Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Chanel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple=false)或多条(multiple=true)记录。从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构。实际上,SDK中的waitForConfirms()方法也是通过SortedSet维护消息序号的。

 

public class Send3 {
	private static final String QUEUE_NAME = "confirm_test_1";
 
	public static void main(String[] args) throws IOException, InterruptedException {
		Connection connections = ConnectionUtils.getConnections();
		Channel channel = connections.createChannel();
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		// 生产者调用confirmSelect 将channel设置为confirm模式
		channel.confirmSelect();
		// 存放未确认的消息标识
		final SortedSet confirmSet = Collections.synchronizedNavigableSet(new TreeSet());
		// 添加通道监听
		channel.addConfirmListener(new ConfirmListener() {
			public void handleAck(long deliverTag, boolean mutiple) throws IOException {
				if (mutiple) {
					System.out.println("---handleAck-------mutiple----");
					confirmSet.headSet(deliverTag + 1).clear();
				} else {
					System.out.println("---handleAck-------mutiple---false");
					confirmSet.remove(deliverTag);
				}
			}
 
			public void handleNack(long deliverTag, boolean mutiple) throws IOException {
				if (mutiple) {
					System.out.println("---handleNack-------mutiple----");
					confirmSet.headSet(deliverTag + 1).clear();
				} else {
					System.out.println("---handleNack-------mutiple---false");
					confirmSet.remove(deliverTag);
				}
			}
 
		});
 
		String msg = "hello confirm msg";
		while (true) {
			long seqNo = channel.getNextPublishSeqNo();
			channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
			confirmSet.add(seqNo);
		}
	}
}
 

 

10.Spring集合rabbitmq

配置:

rabittmq初级教程_第15张图片

生产者:

rabittmq初级教程_第16张图片

 

消费者:

rabittmq初级教程_第17张图片

 

 

 

 

 

 

你可能感兴趣的:(基础知识)