RabbitMQ
MQ全称为Message Queue,即消息队列. 它也是一个队列,遵循FIFO原则.
RabbitMQ官方地址:http://www.rabbitmq.com/
优点:
1.提高响应速度
2.服务异步调用
3.服务之间彻底解耦
3.消除峰值,把并发请求串行化执行
4.请求按顺序执行
6.数据不丢失,提高可用性
安装
- 首先需要erlang语言的环境
RabbitMQ的下载地址:http://www.rabbitmq.com/download.html - 配置环境变量
安装RabbitMQ
下载地址:https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.7.3
在sbin目录下cmd,安装rabbitMQ的管理插件,方便在浏览器端管理RabbitMQ;
rabbitmq-plugins.bat enable rabbitmq_management
安装插件后启动
登录
启动成功 登录RabbitMQ,进入浏览器,输入:http://localhost:15672
初始账号和密码:guest/guest
RabbitMQ–helloworld
1.RabbitMQ的使用
2.helloworld
2.1.导包
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<!--和springboot2.0.5对应-->
<version>5.4.1</version>
</dependency>
2.2.工具类
public class ConnectionUtil {
/**
* 建立与RabbitMQ的连接
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
//定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("127.0.0.1");
//端口
factory.setPort(5672);
//设置账号信息,用户名、密码、vhost
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
// 通过工程获取连接
Connection connection = factory.newConnection();
return connection;
}
}
2.3.发送者
//消息发送者
public class Send {
//队列名称
public static final String QUEUE_NAME = "queue_name";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明队列 队列名称、是否持久化、队列是否独占此连接、队列不再使用时是否自动删除此队列、队列参数
channel.queueDeclare(QUEUE_NAME, true, false,false,null);
//发送消息
String msg = "canola";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
System.out.println("发送成功"+msg);
}
}
2.4.接收端
//消息接受者
public class Rev {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//声明队列 队列名称、是否持久化、队列是否独占此连接、队列不再使用时是否自动删除此队列、队列参数
channel.queueDeclare(Send.QUEUE_NAME, true, false, false, null);
//回调方法
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//int i=1/0;
System.out.println("接收"+new String(body));
//设置手动签收 发生异常不会丢失数据 异常处理完毕再签收
channel.basicAck(envelope.getDeliveryTag(),false );
}
};
//自动签收如果发送异常,会造成数据丢失,而手动签收可以解决数据丢失的问题
//接收消息 true自动签收 false手动签收
channel.basicConsume(Send.QUEUE_NAME, false,consumer );
}
}
RabbitMq-workqueues
work queues与入门程序相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。
应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
1.发送者
//消息发送者
public class Send {
//队列名称
public static final String WORK_QUEUE_NAME = "work_queue_name";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明队列 队列名称、是否持久化、队列是否独占此连接、队列不再使用时是否自动删除此队列、队列参数
channel.queueDeclare(WORK_QUEUE_NAME, true, false,false,null);
//发送消息
String msg = "canola";
channel.basicPublish("", WORK_QUEUE_NAME, null, msg.getBytes());
System.out.println("发送成功"+msg);
}
}
2.接受者–2
需要设置每个接收者消费消息的个数,不然出现异常就会导致这个接受者接收消息堵塞
channel.basicQos(1);
//消息接受者
public class Rev2 {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//声明队列 队列名称、是否持久化、队列是否独占此连接、队列不再使用时是否自动删除此队列、队列参数
channel.queueDeclare(Send.WORK_QUEUE_NAME, true, false, false, null);
//同时只能处理一个消息 不设置的话,一个消费者异常,导致不能接收消息,出现消息堵塞
channel.basicQos(1);
//回调方法
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {//模拟异常
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//int i=1/0;
System.out.println("接收"+new String(body));
//设置手动签收 发生异常不会丢失数据 异常处理完毕再签收
channel.basicAck(envelope.getDeliveryTag(),false );
}
};
//自动签收如果发送异常,会造成数据丢失,而手动签收可以解决数据丢失的问题
//接收消息 true自动签收 false手动签收
channel.basicConsume(Send.WORK_QUEUE_NAME, false,consumer );
}
}
3.接受者–2
//消息接受者
public class Rev2 {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//声明队列 队列名称、是否持久化、队列是否独占此连接、队列不再使用时是否自动删除此队列、队列参数
channel.queueDeclare(Send.WORK_QUEUE_NAME, true, false, false, null);
//同时只能处理一个消息 不设置的话,一个消费者异常,导致不能接收消息,出现消息堵塞
channel.basicQos(1);
//回调方法
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {//模拟异常
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//int i=1/0;
System.out.println("接收"+new String(body));
//设置手动签收 发生异常不会丢失数据 异常处理完毕再签收
channel.basicAck(envelope.getDeliveryTag(),false );
}
};
//自动签收如果发送异常,会造成数据丢失,而手动签收可以解决数据丢失的问题
//接收消息 true自动签收 false手动签收
channel.basicConsume(Send.WORK_QUEUE_NAME, false,consumer );
}
}
发布订阅模型
1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的
交换机–Exchange
- 交换机一方面:接收生产者发送的消息。另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
- Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
- 交换机分类
1.Fanout:广播
将消息交给所有绑定到交换机的队列 all
2.Direct:定向
把消息交给符合指定routing key 的队列 一堆或一个
3.Topic:通配符
把消息交给符合routing pattern(路由模式)的队列 一堆或者一个
1.广播模式
在广播模式下,消息发送流程:
- 1) 可以有多个消费者
- 2) 每个消费者有自己的queue(队列)
- 3) 每个队列都要绑定到Exchange(交换机)
- 4) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
- 5) 交换机把消息发送给绑定过的所有队列
- 6) 队列的消费者都能拿到消息。实现一条消息被多个消费者消费
1.1.发送者
发送者需要声明交换机 ,不需要声明队列,发送消息的时候需要指定交换机,不需要指定routingkey;
//消息发送者
public class Send {
//交换机名称
public static final String EXCHANGE_NAME_FANOUT = "exchange_name_fanout";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机 指定消息发送模式为广播模式
channel.exchangeDeclare(EXCHANGE_NAME_FANOUT, BuiltinExchangeType.FANOUT );
//发送消息
String msg = "canola";
//指定交换机,不需要指定路由
channel.basicPublish(EXCHANGE_NAME_FANOUT, "", null, msg.getBytes());
System.out.println("发送成功"+msg);
}
}
1.2.接受者-1
接受者需要声明队列 , 需要给队列绑定交换机 ,接受者的交换机和消息发送者的交换机要一致。多个消息接受者,声明的队列的名字需要不一样,而交换机的名字需要一样。
//消息接受者
public class Rev {
//队列名称
public static final String QUEUE_NAME_FANOUT = "queue_name_fanout";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME_FANOUT, true, false, false, null);
//绑定交换机 对列名字 交换机
channel.queueBind(QUEUE_NAME_FANOUT, Send.EXCHANGE_NAME_FANOUT, "");
//同时处理一个消息 不设置的话,一个消费者异常,导致不能接收消息,出现消息堵塞
channel.basicQos(1);
//回调方法
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//int i=1/0;
System.out.println("接收"+new String(body));
//设置手动签收 发生异常不会丢失数据 异常处理完毕再签收
channel.basicAck(envelope.getDeliveryTag(),false );
}
};
//自动签收如果发送异常,会造成数据丢失,而手动签收可以解决数据丢失的问题
//接收消息 true自动签收 false手动签收
channel.basicConsume(QUEUE_NAME_FANOUT, false,consumer );
}
}
1.3.接受者-2
//消息接受者
public class Rev2 {
//对列名称
public static final String QUEUE_NAME_FANOUT_2 = "queue_name_fanout_2";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME_FANOUT_2, true, false, false, null);
//绑定交换机
channel.queueBind(QUEUE_NAME_FANOUT_2, Send.EXCHANGE_NAME_FANOUT,"" );
//同时处理一个消息 不设置的话,一个消费者异常,导致不能接收消息,出现消息堵塞
channel.basicQos(1);
//回调方法
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//int i=1/0;
System.out.println("接收"+new String(body));
//设置手动签收 发生异常不会丢失数据 异常处理完毕再签收
channel.basicAck(envelope.getDeliveryTag(),false );
}
};
//自动签收如果发送异常,会造成数据丢失,而手动签收可以解决数据丢失的问题
//接收消息 true自动签收 false手动签收
channel.basicConsume(QUEUE_NAME_FANOUT_2, false,consumer );
}
}
2.定向模式
direct定向模式是交换机根据生产者指定的routingkey,收到消息后去匹配指定routingkey的队列,将消息发送给队列,消费者进行消费
P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
C1:消费者,其所在队列指定了需要routing key 为 error 的消息
C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息
2.1.发送者
//消息发送者
public class Send {
//交换机名称
public static final String EXCHANGE_NAME_DIRECT = "exchange_name_direct";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机 指定消息发送模式为 定向模式
channel.exchangeDeclare(EXCHANGE_NAME_DIRECT, BuiltinExchangeType.DIRECT );
//发送消息
String msg = "canola";
//指定交换机,指定路由 会去匹配和routingkey相同的消费者消费
channel.basicPublish(EXCHANGE_NAME_DIRECT, "info", null, msg.getBytes());
System.out.println("发送成功"+msg);
}
}
2.2.接受者
- 和发送者的routingkey匹配上就会进行消费
- 多个消息接收者,只需要声明不同的队列即可 , 交换机是相同的 ,并且绑定队列的时候要指定队列的routingkey
//消息接受者
public class Rev2 {
//对列名称
public static final String QUEUE_NAME_DIRECT_2 = "queue_name_direct_2";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME_DIRECT_2, true, false, false, null);
//绑定交换机 可以指定多个
channel.queueBind(QUEUE_NAME_DIRECT_2, Send.EXCHANGE_NAME_DIRECT,"info" );
channel.queueBind(QUEUE_NAME_DIRECT_2, Send.EXCHANGE_NAME_DIRECT,"warning" );
//同时处理一个消息 不设置的话,一个消费者异常,导致不能接收消息,出现消息堵塞
channel.basicQos(1);
//回调方法
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//int i=1/0;
System.out.println("接收"+new String(body));
//设置手动签收 发生异常不会丢失数据 异常处理完毕再签收
channel.basicAck(envelope.getDeliveryTag(),false );
}
};
//自动签收如果发送异常,会造成数据丢失,而手动签收可以解决数据丢失的问题
//接收消息 true自动签收 false手动签收
channel.basicConsume(QUEUE_NAME_DIRECT_2, false,consumer );
}
}
通配符
topic的工作模式跟 direct是一样的 , direct定向模式是交换机根据product指定的routingkey,收到消息后去匹配指定routingkey的队列,将消息发送给队列,消费者进行消费 , 只是不一样的是,消费者在绑定队列的时候队列的routingkey可以使用通配符
#
匹配多个
*
匹配一个
3.1.发送者
//消息发送者
public class Send {
//交换机名称
public static final String EXCHANGE_NAME_TOPIC = "exchange_name_topic";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明交换机 指定消息发送模式为 定向模式
channel.exchangeDeclare(EXCHANGE_NAME_TOPIC, BuiltinExchangeType.TOPIC );
//发送消息
String msg = "canola";
//指定交换机,指定路由 会去匹配和routingkey相同的消费者消费
channel.basicPublish(EXCHANGE_NAME_TOPIC, "user.info", null, msg.getBytes());
System.out.println("发送成功"+msg);
}
}
3.2.接受者
//消息接受者
public class Rev {
//队列名称
public static final String QUEUE_NAME_TOPIC = "queue_name_topic";
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME_TOPIC, true, false, false, null);
//绑定交换机 对列名字 交换机 使用*通配符 匹配所以前缀为user的发送者
channel.queueBind(QUEUE_NAME_TOPIC, Send.EXCHANGE_NAME_TOPIC, "user.*");
//同时处理一个消息 不设置的话,一个消费者异常,导致不能接收消息,出现消息堵塞
channel.basicQos(1);
//回调方法
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//int i=1/0;
System.out.println("接收"+new String(body));
//设置手动签收 发生异常不会丢失数据 异常处理完毕再签收
channel.basicAck(envelope.getDeliveryTag(),false );
}
};
//自动签收如果发送异常,会造成数据丢失,而手动签收可以解决数据丢失的问题
//接收消息 true自动签收 false手动签收
channel.basicConsume(QUEUE_NAME_TOPIC, false,consumer );
}
}
持久化–解决数据安全
要将消息持久化,队列、Exchange都要持久化;即使RabbitMQ关闭,数据也不会丢失;
1.发送端
发送端只需在声明交换机和发送消息的时候设置持久化
2.接收端
接收端只需将队列持久化
SpringBoot集成RabbitMQ
1.导包
<!--1.SpringBoot的父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--spirngboot集成rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
2.启动类
//启动类
@SpringBootApplication
public class RabbitMQApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitMQApplication.class);
}
}
3.配置类
@Component
public class RabbitmqConfig {
//邮箱队列名
public static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
//短信队列名
public static final String QUEUE_INFORM_SMS = "queue_inform_sms";
//交换机名字
public static final String EXCHANGE_TOPICS_INFORM = "exchange_topics_inform";
//定义交换机
@Bean(EXCHANGE_TOPICS_INFORM)
public Exchange exchangeTopic() {
//durable(true)持久化,消息队列重启后交换机仍然存在
return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
}
/*******************************************************************/
//声明邮箱队列
@Bean(QUEUE_INFORM_EMAIL)
public Queue queueEmail() {
Queue queue = new Queue(QUEUE_INFORM_EMAIL);
return queue;
}
//绑定邮箱队列到交换机,@Qualifier指定对应的队列或者交换机
@Bean
public Binding BINDING_QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue,
@Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("email.#").noargs();
}
/*******************************************************************/
//声明短信队列
@Bean(QUEUE_INFORM_SMS)
public Queue queueSMS() {
Queue queue = new Queue(QUEUE_INFORM_SMS);
return queue;
}
//绑定短信到交换机
@Bean
public Binding BINDING_QUEUE_INFORM_SMS(@Qualifier(QUEUE_INFORM_SMS) Queue queue,
@Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("sms.#").noargs();
}
}
4.发送端
//发送端
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitMQApplication.class)
public class Send {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test()throws Exception{
for (int i=0;i<10;i++) {
if (i % 2 == 0) {
String EmailMsg = "邮箱";
//交换机名称、路由的前缀以及消息
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM, "email.user", EmailMsg);
}else {
String SMSMsg = "短信";
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPICS_INFORM, "sms.user", SMSMsg);
}
}
Thread.sleep(10000);
}
}
5.接收端
//接收端
@Component
public class Rev {
//监听email队列 queues可以监听多个队列
@RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL})
public void receive_email(String msg, Message message, Channel channel) {
System.out.println(msg);
}
//监听sms队列
@RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_SMS})
public void receive_sms(String msg, Message message, Channel channel) {
System.out.println(msg);
}
}