Mq
消息中间件,用来储存消息
优势:
应用解耦:提高系统的容错性和可维护性
异步提速:提升用户的体验和系统的吞吐量
削峰填谷:提高系统的稳定性
劣势:
系统可用性降低
系统复杂性提高
一致性问题
何时使用mq?
生产者不需要从消费者获得反馈
容忍短暂的不一致性
确实能提高速度
RabbitMQ
RabbitMQ的快速实现
创建两个工作台,生产者和消费者
在生产者中:
package com.huazi.producer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
public class Producer_HelloWorld {
public static void main(String[] args) throws IOException {
//创建链接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("");//默认本机
factory.setPort();//端口 默认5672
factory.setVirtualHost("/");//虚拟机 默认/
factory.setUsername("guest");//用户名 默认guest
factory.setPassword("guest");//密码 默认guest
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
//创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
1.queue:队列名称
2.durable:是否持久化,当mq重启之后还在
3.exclusive:
*是否独占,只能有一个消费者监听该队列
*当连接关闭时,是否删除队列
4.autoDelete;是否自动删除,当没有消费者时自动删除
5.arguments:参数
*/
//如果没有一个hello_world队列,自动创建,有的话不需要创建
channel.queueDeclare("hello_world",true,false,false,null);
/*
basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body)
1.exchange 交换机,简单模式使用默认的”“
2.routingKey:路由名称
3.props:配置信息
4.body;发送信息数据,字节数组
*/
//发送信息
String body="hello_world rabbitmq``````";
channel.basicPublish("","hello_world",null,body.getBytes());
channel.close();
connection.close();
}
}
运行结果如下,rabbitmq可视化界面可以得到:
证明生产者发送信息到mq,mq也接受到了
接下来编写消费者代码,消费者区别在于将发送信息转为接收消息
package com.huazi.comsumer;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Comsumer_HelloWorld {
public static void main(String[] args) throws IOException {
//创建链接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("127.0.0.1");//默认本机
factory.setPort(5672);//端口 默认5672
factory.setVirtualHost("/");//虚拟机 默认/
factory.setUsername("guest");//用户名 默认guest
factory.setPassword("guest");//密码 默认guest
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
//创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
1.queue:队列名称
2.durable:是否持久化,当mq重启之后还在
3.exclusive:
*是否独占,只能有一个消费者监听该队列
*当连接关闭时,是否删除队列
4.autoDelete;是否自动删除,当没有消费者时自动删除
5.arguments:参数
*/
//如果没有一个hello_world队列,自动创建,有的话不需要创建
channel.queueDeclare("hello_world", true, false, false, null);
//接受信息
/* basicConsume(String queue, boolean autoAck, Consumer callback)
1.queue:队列名称
2.autoAck:是否自动确认
3.callback:对象
*/
Consumer consumer=new DefaultConsumer(channel){
/*
回调方法,接受信息会自动执行方法
1.consumerTag 标识
2.envelope 获取信息,交换机,路由
3.properties 配置信息
4.body 数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag"+consumerTag);
System.out.println("envelope"+envelope);
System.out.println("properties"+properties);
System.out.println("body"+new String(body));
}
};
channel.basicConsume("hello_world",true,consumer);
//消费者不需要关闭资源
}
}
接下来是接受的信息
证明消费者从mq中取得了信息
Woorkqueues工作模式
可以提高工作速度
实现:
将路由名称和队列名称进行修改,利用一个for循环设计发送十条数据,在创建两个consumer1和2进行数据的接收
for (int i=1;i<=10;i++) {
String body =i+ "hello_world rabbitmq``````";
channel.basicPublish("", "work_quenens", null, body.getBytes());
}
接受结果如下:
两者互相争抢
Pub/Sub订阅模式
x为交换机,负责转发消息,不具备存储能力
Fanout:广播
Direct:定向
Topic:通配符
实现:生产者
package com.huazi.producer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer_PubSub{
public static void main(String[] args) throws IOException, TimeoutException {
//创建链接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("");//默认本机
factory.setPort(5672);//端口 默认5672
factory.setVirtualHost("/");//虚拟机 默认/
factory.setUsername("guest");//用户名 默认guest
factory.setPassword("guest");//密码 默认guest
//创建连接Connection
Connection connection = factory.newConnection();
//创建Channel
Channel channel = connection.createChannel();
/*
exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments
1.exchange:交换机名称
direct:定向
fanout:扇形广播,发送消息到队列
topic:通配符的方式
headers:参数匹配
2.type:交换机类型
3.durable:是否持久化
4.autoDelete:是否自动删除
5.internal:内部使用,一般false
6.arguments:参数
*/
//创建交换机
String exchangeName="test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,null);
//创建队列
String queue1Name="test_fanout_queue1";
String queue2Name="test_fanout_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//绑定交换机和队列
/*
queueBind(String queue, String exchange, String routingKey)
queue:队列名称
exchange:交换机名称
routingKey:路由键,绑定规则
如果交换机的类型是fanout,那么路由键就为空
*/
channel.queueBind(queue1Name,exchangeName,"");
channel.queueBind(queue2Name,exchangeName,"");
//发送消息
String body="日志信息:张胜男调用了findAll方法。。。日志级别为 info";
channel.basicPublish(exchangeName,"",null,body.getBytes());
//释放资源
channel.close();
connection.close();
}
}
和之前不一样的是得添加一个交换机以及声明交换机的类型,创建队列以及绑定交换机
接下来设置两个消费者,分别是消费者1和消费者2,两者的区别就在于读取队列不同
package com.huazi.comsumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Comsumer_PubSub1 {
public static void main(String[] args) throws IOException {
//创建链接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置参数
factory.setHost("127.0.0.1");//默认本机
factory.setPort(5672);//端口 默认5672
factory.setVirtualHost("/");//虚拟机 默认/
factory.setUsername("guest");//用户名 默认guest
factory.setPassword("guest");//密码 默认guest
//创建连接Connection
Connection connection = null;
try {
connection = factory.newConnection();
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
//创建Channel
Channel channel = connection.createChannel();
//创建队列Queue
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
1.queue:队列名称
2.durable:是否持久化,当mq重启之后还在
3.exclusive:
*是否独占,只能有一个消费者监听该队列
*当连接关闭时,是否删除队列
4.autoDelete;是否自动删除,当没有消费者时自动删除
5.arguments:参数
*/
//如果没有一个hello_world队列,自动创建,有的话不需要创建
//channel.queueDeclare("work_quenens", true, false, false, null);
String queue1Name="test_fanout_queue1";
String queue2Name="test_fanout_queue2";
//接受信息
/* basicConsume(String queue, boolean autoAck, Consumer callback)
1.queue:队列名称
2.autoAck:是否自动确认
3.callback:对象
*/
Consumer consumer=new DefaultConsumer(channel){
/*
回调方法,接受信息会自动执行方法
1.consumerTag 标识
2.envelope 获取信息,交换机,路由
3.properties 配置信息
4.body 数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag"+consumerTag);
// System.out.println("envelope"+envelope);
// System.out.println("properties"+properties);
System.out.println("body"+new String(body));
System.out.println("将日志打印");
}
};
channel.basicConsume("test_fanout_queue1",true,consumer);
//消费者不需要关闭资源
}
}
Routing工作模式
将交换机的类型改为定向,分别给两个线程绑定路由键,当发送error消息时,两个队列都呢个接收到,
当发送消息时info或者是warning时,只有第二个队列可以接收到
channel.queueBind(queue1Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
channel.queueBind(queue2Name,exchangeName,"info");
Topics模式 通配符模式(更加灵活)
通配符模式更加灵活,只要文件的命名格式一样就可传送到对应的队列,非常的方便
channel.queueBind(queue1Name,exchangeName,"#.error");
channel.queueBind(queue1Name,exchangeName,"order.#");
channel.queueBind(queue2Name,exchangeName,"#.#");