MQ的特点:
MQ是秉承先进先出的过程,消息队列的顺序在入队的时候就基本已经确定了,一般是不需人工干预的。而且,最
重要的是,数据是只有一条数据在使用中。 这也是MQ在诸多场景被使用的原因。
发布订阅是一种很高效的处理方式,如果不发生阻塞,基本可以当做是同步操作。这种处理方式能非常有效的提升
服务器利用率,这样的应用场景非常广泛。
持久化确保MQ的使用不只是一个部分场景的辅助工具,而是让MQ能像数据库一样存储核心的数据。
在现在大流量、大数据的使用场景下,只支持单体应用的服务器软件基本是无法使用的,支持分布式的部署,才能
被广泛使用。而且,MQ的定位就是一个高性能的中间件。
发布者发布消息传到交换机上队列通过定义的订阅号匹配自己订阅的消息,进入队列进行等待,订阅者从响应的队列中一次得到订阅的消息。
使用MQ主要是为了加快访问的速度,队列就像一个缓存机制一样将发布者发布的数据放到队列中进行缓存不会立即的发送给订阅者,只是在系统空闲时再发送给订阅者缓解大数据量是的系统压力,而且在读取数据时可以从队列里直接拿数据,例如:统计分析时要得到数据可以直接从队列中得到数据可以进行流量削峰。
消息发送原理
首先你必须连接到Rabbit才能发布和消费消息,那怎么连接和发送消息的呢?
你的应用程序和Rabbit Server之间会创建一个TCP连接,一旦TCP打开,并通过了认证,认证就是你试图连接Rabbit之前发送的Rabbit服务器连接信息和用户名和密码,有点像程序连接数据库,使用Java有两种连接认证的方式,后面代码会详细介绍,一旦认证通过你的应用程序和Rabbit就创建了一条AMQP信道(Channel)。
信道是创建在“真实”TCP上的虚拟连接,AMQP命令都是通过信道发送出去的,每个信道都会有一个唯一的ID,不论是发布消息,订阅队列或者介绍消息都是通过信道完成的。
为什么不通过TCP直接发送命令?
对于操作系统来说创建和销毁TCP会话是非常昂贵的开销,假设高峰期每秒有成千上万条连接,每个连接都要创建一条TCP会话,这就造成了TCP连接的巨大浪费,而且操作系统每秒能创建的TCP也是有限的,因此很快就会遇到系统瓶颈。
如果我们每个请求都使用一条TCP连接,既满足了性能的需要,又能确保每个连接的私密性,这就是引入信道概念的原因。
想要真正的了解Rabbit有些名词是你必须知道的。
包括:ConnectionFactory(连接管理器)、Channel(信道)、Exchange(交换器)、Queue(队列)、RoutingKey(路由键)、BindingKey(绑定键)。
ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用;
Channel(信道):消息推送使用的通道;
Exchange(交换器):用于接受、分配消息;
Queue(队列):用于存储生产者的消息;
RoutingKey(路由键):用于把生成者的数据分配到交换器上;
BindingKey(绑定键):用于把交换器的消息绑定到队列上;
消息持久化
Rabbit队列和交换器有一个不可告人的秘密,就是默认情况下重启服务器会导致消息丢失,那么怎么保证Rabbit在重启的时候不丢失呢?答案就是消息持久化。
当你把消息发送到Rabbit服务器的时候,你需要选择你是否要进行持久化,但这并不能保证Rabbit能从崩溃中恢复,想要Rabbit消息能恢复必须满足3个条件:
投递消息的时候durable设置为true,消息持久化,代码:channel.queueDeclare(x, true, false, false, null),参数2设置为true持久化;
设置投递模式deliveryMode设置为2(持久),代码:channel.basicPublish(x, x, MessageProperties.PERSISTENT_TEXT_PLAIN,x),参数3设置为存储纯文本到磁盘;
消息已经到达持久化交换器上;
消息已经到达持久化的队列;
持久化工作原理
Rabbit会将你的持久化消息写入磁盘上的持久化日志文件,等消息被消费之后,Rabbit会把这条消息标识为等待垃圾回收。
持久化的缺点
消息持久化的优点显而易见,但缺点也很明显,那就是性能,因为要写入硬盘要比写入内存性能较低很多,从而降低了服务器的吞吐量,尽管使用SSD硬盘可以使事情得到缓解,但他仍然吸干了Rabbit的性能,当消息成千上万条要写入磁盘的时候,性能是很低的。
所以使用者要根据自己的情况,选择适合自己的方式。
流量削峰的介绍来自于:https://blog.csdn.net/u014231523/article/details/83005788 这里又具体的流量削峰的简介与说明
下面上代码:为大家介绍了解一下MQ
(也是在学习视频中学习的希望相互学习)
创建MQ连接
下面有很多要使用到所以提出来使用
public class ConnectionUtiles {
/**
* 获取MQ的连接
*/
public static Connection getConnection() throws IOException, TimeoutException {
//定义一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("127.0.0.1");
//MQ连接的端口 5672 设置端口号
factory.setPort(5672);
//vhost 在rabbitmq界面上获得 设置数据库
factory.setVirtualHost("/vhost_ly");
//设置用户名 界面上分配的用户名
factory.setUsername("/user_ly");
//设置密码 分配的密码
factory.setPassword("123");
return factory.newConnection();
}
}
简单的消息队列
发送方------->队列------>接收方 一对一的发送消息 (这种比较单一发送与接收等待时间长,且无法达到想要的效果)
发送方
public class Sender {
private static final String QUEUE_NAME="test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取一个连接
Connection connection = ConnectionUtiles.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//创建队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String msq = "hello simple !";
channel.basicPublish("",QUEUE_NAME,null,msq.getBytes());
System.out.println("--send msq"+msq);
//关闭数据关闭连接
channel.close();
connection.close();
}
}
接收方:
public class Receiver{
/**
* 简单队列的不足
* 耦合性高,生产者一一对应消费者(如果我想有多个消费者消费队列中消息,这时就不行了)
* 队列名变更,这时得同时变更
*
* simple队列是一一对应的,而我们实际开发,生产者发送消息是毫不费力的,
* 而消费者一般是跟业务相结合的,消费者接收到消息之后就需要处理 可能需要花费时间,
* 这时队列就会积压了很多消息
*/
//定义队列名称 定义一次后已经启动不能修改,修改将报错,因为一个名称只能定义一次,除非再定义其他的名称
private static final String QUEUE_NAME="test_simple_queue";
@SuppressWarnings("deprecation")
public static void main(String[] args) throws IOException, TimeoutException {
//获取一个连接
Connection connection = ConnectionUtiles.getConnection();
//创建频道
Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
//获取到达的消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException{
String msg = new String(body,"utf-8");
System.out.println("new api recv:"+msg);
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
公平分发 能者多劳
-----|----消费者1
成产者-----队列 -----|
-----|----消费者2
发送方
public class Send {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtiles.getConnection();
//获取channel
Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
/**
* 每个消费者 发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
* 限制发送给同一个消费者 不能超过一条消息
*/
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//发送消息
for (int i = 0;i<50;i++){
String msg = "hello "+i;
System.out.println("WQ send"+msg);
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
Thread.sleep(i+20);
}
channel.close();
connection.close();
}
}
订阅者
public class Recv1 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtiles.getConnection();
//获取channel
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,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{
String msg = new String(body,"utf-8");
System.out.println("new api recv1:"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("recv1 done");
//将自动的改为手动每次告诉队列我只接受的数量
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自动应答改为false
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
public class Recv2 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtiles.getConnection();
//获取channel
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,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{
String msg = new String(body,"utf-8");
System.out.println("new api recv2:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("recv2 done");
//手动回执一个消息
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
采用topic的模式
通配符的模式进行匹配。
|----good.add--->队列------>订阅者1
发送者----Key(good.add)--->交换机-------|
|----good.#------>队列----->订阅者2
发送者
public class Send {
//定义交换机
private static final String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtiles.getConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic");//分发
//发送消息
String msg = "商品.... topic";
channel.basicPublish(EXCHANGE_NAME,"goods.add",null,msg.getBytes());
System.out.println("----send :"+msg);
channel.close();
connection.close();
}
}
订阅者1
public class Recv1 {
private static final String EXCHANGE_NAME = "test_exchange_topic";
private static final String QUEUE_NAME = "test_queue_topic_1";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtiles.getConnection();
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");
channel.basicQos(1);//保证一次只发一个
//定义一个消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
//获取到达的消息 一旦有消息就会触发该方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException{
String msg = new String(body,"utf-8");
System.out.println("new api recv1:"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("recv1 done");
//将自动的改为手动每次告诉队列我只接受的数量
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自动应答改为false
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
订阅者2
public class Recv2 {
private static final String EXCHANGE_NAME = "test_exchange_topic";
private static final String QUEUE_NAME = "test_queue_topic_2";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtiles.getConnection();
final Channel channel = connection.createChannel();
//队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
channel.basicQos(1);//保证一次只发一个
//定义一个消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
//获取到达的消息 一旦有消息就会触发该方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException{
String msg = new String(body,"utf-8");
System.out.println("new api recv2:"+msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("recv2 done");
//将自动的改为手动每次告诉队列我只接受的数量
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自动应答改为false
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}