public class RabbitmqUtils {
private static final ConnectionFactory connectionFactory;
static {
//创建连接mq的连接工厂对象,重量级对象,类加载时创建一次即可
connectionFactory = new ConnectionFactory();
//设置连接rabbitmq的主机
connectionFactory.setHost("192.168.200.130");
//设置端口号
connectionFactory.setPort(5672);
//设置连接的虚拟主机
connectionFactory.setVirtualHost("/ems");
//设置访问虚拟主机的用户名和密码
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
}
//获取连接对象
public static Connection getConnection(){
try {
return connectionFactory.newConnection();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
return null;
}
//关闭通道和连接
public static void close(Channel channel, Connection connection){
if(channel != null){
try {
channel.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
if(connection != null){
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class Provider {
@Test
public void ConnnetionRabbitMq() throws IOException, TimeoutException {
//创建对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接的rabbitmq主机
connectionFactory.setHost("192.168.200.130");
//设置连接的虚拟主机
connectionFactory.setVirtualHost("/ems");
//设置访问虚拟主机的哦用户名和密码
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
//获取连接对象
Connection connection = connectionFactory.newConnection();
//获取来凝结中通道
Channel channel = connection.createChannel();
//参数一:队列名称
//参数二:是否持久化
//参数三:是否独占队列
//参数四:是否在消息后自动删除队列
//参数五:附加额外参数
channel.queueDeclare("hello", false, false, false, null);
channel.basicPublish("", "hello", null, "hello RabbitMq".getBytes());
channel.close();
connection.close();
}
}
public class Consumer {
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//获取连接对象
connection = RabbitmqUtils.getConnection();
//获取通道
if (connection != null) {
channel = connection.createChannel();
//通道绑定对应消息队列
/*
* 参数1 queue:队列名称(不存在自动创建)
* 参数2 durable:用来定义队列特性是否需要持久化(为true该队列将在服务器重启后保留下来)
* 参数3 exclusive:是否独占队列(为true仅限此连接)
* 参数4 autoDelete:是否在消费完成不再使用后自动删除队列
* 参数5 arguments:队列的其他属性(构造参数)
* */
channel.queueDeclare("hello", false, false, false, null);
//消费消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
//获取消息并且处理。此方法类似于事件监听,有消息时会被自动调用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("message:" + new String(body)); //body即消息体
}
};
/*
* 参数1 queue:队列名称
* 参数2 autoAck:开启消息的自动确认机制
* 参数3 Consumer callback:消费时的回调接口
* */
channel.basicConsume("hello", true, defaultConsumer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("消费者消费消息完成......");
//RabbitmqUtils.close(channel,connection);
}
}
}
当消息处理比较耗时的时候,可能生产消息的速度大于消费速度,堆积越来越多无法得到及时的处理,可以使用work模型,让多个消费者绑定在一个队列,共同消费队列中的消息。队列中的消息一旦消费就会消失,因此任务不会被重复执行。
public class Provider {
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//获取连接对象
connection = RabbitmqUtils.getConnection();
if (connection != null) {
//获取通道对象
channel = connection.createChannel();
//声明队列
channel.queueDeclare("work",true,false,false,null);
//生产消息
String message = "";
for (int i = 1; i <= 20; i++) {
message = "work queues,id:"+i;
channel.basicPublish("","work",null,message.getBytes());
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
RabbitmqUtils.close(channel,connection);
}
}
}
public class Consumer1 {
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//获取连接
connection = RabbitmqUtils.getConnection();
if (connection != null) {
//获取通道
channel = connection.createChannel();
//声明队列
channel.queueDeclare("work",true,false,false,null);
//消费消息
channel.basicConsume("work",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Consumer-1:"+new String(body));//打印消息
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Consumer2 {
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//获取连接
connection = RabbitmqUtils.getConnection();
if (connection != null) {
//获取通道
channel = connection.createChannel();
//声明队列
channel.queueDeclare("work",true,false,false,null);
//消费消息
channel.basicConsume("work",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Consumer-2:"+new String(body));//打印消息
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
发布/订阅模式fanout下的消息发送流程:
可以有多个消费者
每个消费者对应一个临时队列(queue)
每个队列都要绑定到交换机(exchange)
生产者只能将消息发送到交换机,交换机来决定发送给哪个队列
交换机把消息发送给绑定过的所有队列
队列的消费者都能拿到消息,实现一条消息被多个消费者消费
public class Provider {
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
//获取连接对象
connection = RabbitmqUtils.getConnection();
try {
if (connection != null) {
//获取通道对象
channel = connection.createChannel();
//将通道声明指定交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare("logs","fanout");
//发布消息(指定交换机)
String message = "fanout message";
channel.basicPublish("logs","",null,message.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源
RabbitmqUtils.close(channel,connection);
}
}
}
public class Consumer1 {
public static void main(String[] args) {
try {
//获取连接
Connection connection = RabbitmqUtils.getConnection();
if (connection != null) {
//获取通道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare("logs","fanout");
//临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机和队列 队列名称 交换机名称 路由键
channel.queueBind(queue,"logs","");
//消费消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Consumer1: "+new String(body)); //打印消息
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在Fanout模式中,一条消息会被所有订阅的队列都消费。但在某些场景下,希望不同的消息被不同功能的队列消费。这时需要用到Direct类型的Exchange
可以有选择性的订阅消息。例如,只将严重错误消息定向到日志文件(以节省磁盘空间),同时仍然能够打印所有控制台上的日志消息。
在Direct模型下:
队列与交换机的绑定,不能是任意绑定,而需要指定一个RoutingKey(路由键)
消息的发送方在向Exchange发送消息时,也必须指定消息的RoutingKey
Exchange不再把消息交给每一个绑定的队列,而是根据消息的RoutingKey进行判断。之后队列的RoutingKey和消息的RoutingKey一致,才会接收消息
public class Provider {
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//获取连接
connection = RabbitmqUtils.getConnection();
if (connection != null) {
//获取通道
channel = connection.createChannel();
//通过通道声明交换机 交换机类型为direct
channel.exchangeDeclare("logs_direct","direct");
//发送消息
String routingKey = "info";
String message = "direct--routingKey,routingKey:"+routingKey;
//交换机名称 路由键 消息其他属性 消息具体内容
channel.basicPublish("logs_direct",routingKey,null,message.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
RabbitmqUtils.close(channel,connection);
}
}
}
public class Consumer1 {
public static void main(String[] args) {
try {
//获取连接
Connection connection = RabbitmqUtils.getConnection();
if (connection != null) {
//获取通道
Channel channel = connection.createChannel();
//通道声明交换机与交换机类型
channel.exchangeDeclare("logs_direct","direct");
//临时队列
String queue = channel.queueDeclare().getQueue();
//基于route key绑定队列和交换机
channel.queueBind(queue,"logs_direct","error");
//消费消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Consumer1:"+new String(body));
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Consumer2 {
public static void main(String[] args) {
try {
//获取连接
Connection connection = RabbitmqUtils.getConnection();
if (connection != null) {
//获取通道
Channel channel = connection.createChannel();
//声明交换机与交换机类型
channel.exchangeDeclare("logs_direct","direct");
//获取临时队列
String queue = channel.queueDeclare().getQueue();
//基于route key绑定队列与交换机
channel.queueBind(queue,"logs_direct","info");
channel.queueBind(queue,"logs_direct","error");
channel.queueBind(queue,"logs_direct","warning");
//消费消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Consumer2:"+new String(body));
}
};
channel.basicConsume(queue,true,defaultConsumer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Topic类型的Exchange和Direct相比,都可以根据routingkey把消息路由到不同队列。只不过Topic类型Exchange可以让队列在绑定routingkey时使用通配符。这种routingkey一般由一个或多个单词组成。多个单词之间以"."分隔。例如item.insert
通配符:
*(star):可以代替一个单词
#(hash):可以代替零个或多个单词
public class Provider {
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
//获取连接
connection = RabbitmqUtils.getConnection();
if (connection != null) {
//获取通道
channel = connection.createChannel();
//声明交换机及交换机类型
channel.exchangeDeclare("topics","topic");
//发布消息
String routingKey = "user.save";
String message = "hello topic,routingKey:"+routingKey;
channel.basicPublish("topics",routingKey,null,message.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源
RabbitmqUtils.close(channel,connection);
}
}
}
public class Consumer1 {
public static void main(String[] args) {
try {
//获取连接
Connection connection = RabbitmqUtils.getConnection();
//获取通道
if (connection != null) {
Channel channel = connection.createChannel();
//声明交换机与交换机类型
channel.exchangeDeclare("topics","topic");
//生成临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机与队列 使用通配符形式routingKey
channel.queueBind(queue,"topics","user.*");
//消费消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Consumer1:"+new String(body)); //打印消息
}
};
channel.basicConsume(queue,true,defaultConsumer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Consumer2 {
public static void main(String[] args) {
try {
//获取连接
Connection connection = RabbitmqUtils.getConnection();
//获取通道
if (connection != null) {
Channel channel = connection.createChannel();
//声明交换机与交换机类型
channel.exchangeDeclare("topics","topic");
//生成临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机与队列 使用通配符形式routingKey
channel.queueBind(queue,"topics","user.#");
//消费消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Consumer2:"+new String(body)); //打印消息
}
};
channel.basicConsume(queue,true,defaultConsumer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
创建一个spring boot的项目,导入rabbitmq的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
配置yml文件
spring:
application:
name: rabbitmq-springboot
rabbitmq:
host: 192.168.200.130
port: 5672
username: ems
password: 123
virtual-host: /ems
@SpringBootTest(classes = SpringbootRabbitmqTestApplication.class)
@RunWith(SpringRunner.class)
class SpringbootRabbitmqTestApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
//hello world
@Test
public void test() {
rabbitTemplate.convertAndSend("hello","hello world");
}
}
注意需要有消费者才会创建队列
@Component
@RabbitListener(queuesToDeclare = @Queue("hello"))
public class HelloConsumer {
@RabbitHandler
public void receive(String message){
System.out.println("message="+message);
}
}
@Test
public void testWork(){
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("work","hello work");
}
}
@Component
public class WorkConsumer {
//第一个消费者
@RabbitListener(queuesToDeclare = @Queue("work"))
public void receive1(String message){
System.out.println("message1"+message);
}
//第二个消费者
@RabbitListener(queuesToDeclare = @Queue("work"))
public void receive2(String message){
System.out.println("message2"+message);
}
}
//fanout
@Test
public void testFanout(){
rabbitTemplate.convertAndSend("logs","","Fanout模型.....");
}
@Component
public class FanoutComsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(value = "logs",type = "fanout")
)
})
public void comsumer1(String message){
System.out.println("message1="+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(value = "logs",type = "fanout")
)
})
public void comsumer2(String message){
System.out.println("message2="+message);
}
}
@Test
public void testRoute(){
rabbitTemplate.convertAndSend("directs","info","Route模型.....");
}
@Component
public class RouteCustomer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue, //临时队列
exchange = @Exchange(value = "directs",type = "direct"), //交换机名称和类型
key = {"info","error","warning"}
)
})
public void consumer1(String message){
System.out.println("Consumer1:"+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue, //临时队列
exchange = @Exchange(value = "directs",type = "direct"), //交换机名称和类型
key = {"error"}
)
})
public void consumer2(String message){
System.out.println("Consumer2:"+message);
}
}
//topic
@Test
public void testTopic() {
rabbitTemplate.convertAndSend("topics", "user.hello", "topic模型....");
}
@Component
public class TopicConsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(name = "topics",type = "topic"),
key = {"user.*"}
)
})
public void consumer1(String message){
System.out.println("Consumer1:"+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(name = "topics",type = "topic"),
key = {"user.#","order.#"}
)
})
public void consumer2(String message){
System.out.println("Consumer2:"+message);
}
}
用户注册后,需要发注册邮件和注册短信,传统的做法有两种:串行或并行
串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回客户端。
存在问题:邮件短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西
串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回客户端。
存在问题:邮件短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西
并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册信息。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
引入消息队列,将不是必须的业务逻辑(发送短信/邮件)进行异步处理。改造后的架构如下:
引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列处理后,响应时间是串行的3倍,是并行的2倍
场景:购物节用户下单后,订单系统需要通知库存系统。
传统做法:订单系统直接调用库存系统的接口
传统模式缺点:
如果订单系统无法访问,订单系统则失败
订单系统与库存系统耦合度高
引入消息队列
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
库存系统:订阅下单的消息,获取下单消息,进行库存操作。就算库存系统出现故障,也不会影响正常下单。下单之后,订单系统写入消息队列就不再需要关心后续操作,不会导致消息丢失。实现订单系统和库存系统的应用解耦
场景:秒杀活动,一般因为流量过大,可能导致应用宕机。为了解决此问题,一般在应用前端加入消息队列
流程:
服务器接收到用户请求后,首先写入到消息队列。加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面。
秒杀业务可根据消息队列中的请求消息,做后续处理
作用:
可以控制活动人数,当请求超过一定阈值时,直接丢弃
可以缓解短时间的高流量压垮应用(应用按自己最大处理能力获取订单)
优点:在特殊场景下能获得对应好处
缺点:
系统可用性降低:系统引入的外部依赖越多,越容易挂掉。MQ在系统中充当一个中间人的身份,如果MQ失联,整个系统肯定会出现问题
系统复杂性提高:在使用消息队列的过程中,难免会出现生产者、消费者或MQ宕机不可用的情况。随之而来的问题就是消息重复、消息乱序、消息堆积等问题。而这些问题都需要我们来控制
一致性问题:多个系统进行数据库操作,如果其中一个写库失败,就会导致数据不一致问题。