1.任务异步处理
2.应用程序解耦
3.削峰填谷
例子(来源于黑马视频教程):
如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发量再高就容易宕机。低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以上,这个时候数据库肯定卡死了。
消息被MQ保存起来了,然后系统就可以按照自己的消费能力来消费,比如每秒1000个数据,这样慢慢写入数据库,这样就不会卡死数据库了。
但是使用了MQ之后,限制消费消息的速度为1000,但是这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被“削”掉了。但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000QPS,直到消费完积压的消息,这就叫做“填谷
1、amqp协议:
注解:一个virtual Host 相当于一个数据库,不同的应用连接不同的虚拟机
-简单模式
生产者和消费者通过消息队列直连
package rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.eclipse.emf.ecore.util.EContentAdapter;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setVirtualHost("test");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
Channel channel =connection.createChannel();
// 通道绑定消息队列
// 参数1:队列名称 参数2:是否持久化 参数3:是否独占队列,如果独占其他连接连接不上 参数4:消费完毕是否删除队列
// 参数5:其他参数
channel.queueDeclare("test",false,false,false,null);
// 发布消息
// 参数1:交换机 参数2:队列 参数3:属性 参数4:消息内容
channel.basicPublish("","test",null,"hello".getBytes());
channel.close();
connection.close();
}
}
package rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setVirtualHost("test");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
Channel channel =connection.createChannel();
// 通道绑定消息队列
// 参数1:队列名称 参数2:是否持久化 参数3:是否独占队列,如果独占其他连接连接不上 参数4:消费完毕是否删除队列
// 参数5:其他参数
channel.queueDeclare("test",false,false,false,null);
// 发布消息
// 参数1:队列 参数2:是否自动确认消息,实际开发中一般手动确认 参数3:消费回调
channel.basicConsume("test",true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("收到消息:" + new String(body));
}
});
// 不关闭一直监听队列
// channel.close();
// connection.close();
}
}
// 设置消息持久化
channel.basicPublish("","test", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello".getBytes());
for (int i = 0; i < 3; i++) {
channel.basicPublish("","test", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello".getBytes());
}
为true:消息自动确认,消息发送过来的时候就确认,如果此时rabbit挂掉则消息丢失。
为fasle:手动确认,业务逻辑执行完毕则确认
// 设置一次消费一条
channel.basicQos(1);
channel.basicConsume("test",true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("收到消息:" + new String(body));
// 参数1:确认消息的tag
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
package rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import org.eclipse.emf.ecore.util.EContentAdapter;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setVirtualHost("test");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
Channel channel =connection.createChannel();
// 参数1:交换机名称 参数2:交换机类类型 fanout 广播
channel.exchangeDeclare("exchange","fanout");
channel.queueDeclare("test",false,false,false,null);
// 发布消息
// 参数1:交换机 参数2:队列 参数3:属性 参数4:消息内容
for (int i = 0; i < 3; i++) {
channel.basicPublish("exchange","test", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello".getBytes());
}
channel.close();
connection.close();
}
}
package rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setVirtualHost("test");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
Channel channel =connection.createChannel();
// 通道绑定消息队列
channel.exchangeDeclare("exchange","fanout");
// 临时队列
String q = channel.queueDeclare().getQueue();
// 绑定交换机
channel.queueBind(q,"exchange","");
// 发布消息
channel.basicQos(1);
channel.basicConsume(q,true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("收到消息:" + new String(body));
// 参数1:确认消息的tag
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
// 不关闭一直监听队列
// channel.close();
// connection.close();
}
}
注解:此种模式多个消费者都能消费到等价的消息,由于测试是用的临时队列消费完毕及删除,所以第二次发送消息的时候不会消费到消息。
-路由模式
发送消息,不同的消费者消费不同的消息
package rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import org.eclipse.emf.ecore.util.EContentAdapter;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setVirtualHost("test");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
Channel channel =connection.createChannel();
// 参数1:交换机名称 参数2:交换机类类型 fanout 广播
channel.exchangeDeclare("rute","direct",true);
// 发布消息
// 参数1:交换机 参数2:队列 参数3:属性 参数4:消息内容
for (int i = 0; i < 3; i++) {
channel.basicPublish("rute","error", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello error".getBytes());
}
channel.close();
connection.close();
}
}
package rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setVirtualHost("test");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
Channel channel =connection.createChannel();
// 通道绑定消息队列
channel.exchangeDeclare("rute","direct",true);
// 临时队列
String q = channel.queueDeclare().getQueue();
// 绑定交换机
channel.queueBind(q,"rute","error");
// 发布消息
channel.basicConsume(q,true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("收到消息:" + new String(body));
// 参数1:确认消息的tag
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
// 不关闭一直监听队列
// channel.close();
// connection.close();
}
}
// 创建连接
Connection connection = connectionFactory.newConnection();
Channel channel =connection.createChannel();
// 通道绑定消息队列
channel.exchangeDeclare("rute","direct",true);
// 临时队列
String q = channel.queueDeclare().getQueue();
// 绑定交换机
channel.queueBind(q,"rute","info");
其本质和direct没有区别,只是路由的关键字变为了通配符
此时队列命名:xxx.xxx.xx以点号分隔
*:匹配一个字符
e;*.user.* 路由关键字为三个单词,第二个为user
#:匹配多个字符
e:login.# 路由关键字以login开头就行了如login.uer.save.info
producer
// 参数1:交换机名称 参数2:交换机类类型 fanout 广播
channel.exchangeDeclare("topic","topic",true);
// 发布消息
// 参数1:交换机 参数2:队列 参数3:属性 参数4:消息内容
for (int i = 0; i < 3; i++) {
channel.basicPublish("topic","info.error", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello error".getBytes());
}
for (int i = 0; i < 3; i++) {
channel.basicPublish("topic","info.save", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello save".getBytes());
}
for (int i = 0; i < 3; i++) {
channel.basicPublish("topic","info.save.txt", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello save txt".getBytes());
}
channel.close();
connection.close();
consumer
channel.exchangeDeclare("topic","topic",true);
// 临时队列
String q = channel.queueDeclare().getQueue();
// 绑定交换机
channel.queueBind(q,"topic","info.*");
// 发布消息
channel.basicConsume(q,true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("收到消息:" + new String(body));
// 参数1:确认消息的tag
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
注解:消费者没有消费info save txt的三条消息,因为路由绑定为info.*匹配两个字符且第一个字符为info。
1、简单模式
// 利用spring boot带的RabbitTemplate 进行操作
producer
@Component
public class Producer implements CommandLineRunner {
@Autowired
RabbitTemplate rabbitTemplate;
@Override
public void run(String... args) throws Exception {
producer();
}
public void producer() {
rabbitTemplate.convertAndSend("hello","hello world");
}
}
consumer
package springbootra;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queuesToDeclare = @Queue("hello"))
public class Consumer {
// 表示收到消息用此方法处理
@RabbitHandler
public void consum(String msg){
System.out.println("hello");
System.out.println(msg);
}
}
注解:
@RabbitListener:监听队列
queuesToDeclare = @Queue(“hello”):申明队列,对应的参数设置参考下图,注意参数值皆为字符串
2、work模式
provider
@Component
public class Producer implements CommandLineRunner {
@Autowired
RabbitTemplate rabbitTemplate;
@Override
public void run(String... args) throws Exception {
producer();
}
public void producer() {
for (int i = 0; i < 6 ; i++) {
rabbitTemplate.convertAndSend("hello","hello world");
}
}
}
consumer
@Component
public class Consumer {
// 表示收到消息用此方法处理
@RabbitListener(queuesToDeclare = @Queue("hello"))
@RabbitHandler
public void consum1(String msg){
System.out.println(msg +"consumer1");
}
// 表示收到消息用此方法处理
@RabbitListener(queuesToDeclare = @Queue("hello"))
@RabbitHandler
public void consum2(String msg){
System.out.println(msg + "consumer2");
}
}
3、广播模式
package springbootra;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Properties;
@Component
public class Producer implements CommandLineRunner {
@Autowired
RabbitTemplate rabbitTemplate;
@Override
public void run(String... args) throws Exception {
producer();
}
public void producer() {
for (int i = 0; i < 6 ; i++) {
rabbitTemplate.convertAndSend("springexchange","","hello world" + i);
}
}
}
package springbootra;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@RabbitListener(bindings = {
@QueueBinding(
//不设置参数表示临时队列
value = @Queue,
// 绑定交换机
exchange = @Exchange(value = "springexchange", type = "fanout")
)
}
)
public void consum1(String msg){
System.out.println(msg +"consumer1");
}
@RabbitListener(bindings = {
@QueueBinding(
//不设置参数表示临时队列
value = @Queue,
// 绑定交换机
exchange = @Exchange(value = "springexchange", type = "fanout")
)
}
)
public void consum2(String msg){
System.out.println(msg + "consumer2");
}
}
4.路由模式
package springbootra;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Properties;
@Component
public class Producer implements CommandLineRunner {
@Autowired
RabbitTemplate rabbitTemplate;
@Override
public void run(String... args) throws Exception {
producer();
}
public void producer() {
for (int i = 0; i < 2 ; i++) {
rabbitTemplate.convertAndSend("springexchanged","info","hello world info" );
}
for (int i = 0; i < 2 ; i++) {
rabbitTemplate.convertAndSend("springexchanged","error","hello world error" );
}
}
}
package springbootra;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@RabbitListener(bindings = {
@QueueBinding(
//不设置参数表示临时队列
value = @Queue,
// 绑定交换机,默认type为direct
exchange = @Exchange(value = "springexchanged"),
key = {
"error"}
)
}
)
public void consum1(String msg){
System.out.println(msg +" consumer1");
}
@RabbitListener(bindings = {
@QueueBinding(
//不设置参数表示临时队列
value = @Queue,
// 绑定交换机,默认type为direct
exchange = @Exchange(value = "springexchanged"),
key = {
"info"}
)
}
)
public void consum2(String msg){
System.out.println(msg + " consumer2");
}
}
5、动态路由
@RabbitListener(bindings = {
@QueueBinding(
//不设置参数表示临时队列
value = @Queue,
// 绑定交换机,默认type为direct
exchange = @Exchange(name = "springexchanged" ,type = "topic"),
key = {
"error.*"}
)
}
)
注解:代码同direct,不在重复。只需要注意绑定交换机类型即可。