MQ的概念
- 概念:
Message Queue是用于存储消息的容器,多用于分布式系统之间的通信。
- 优势
- 解耦:
不同系统之间的耦合度越高,容错性和可维护性就越低。
引入中间件可以降低不同系统之间的耦合度,提高容错和可维护性。
- 异步提速
不引入MQ时,一个订单操作需要等待库存、支付、物流等系统的返回结果最后数据库完成交互,假设其耗时如上共需920ms。
在引入MQ做异步提速后(提高了用户体验和系统吞吐量),订单系统只需和MQ、DB进行交互,就用户体验而言仅需25ms即可获得响应,其余操作通过异步的方式执行。 - 削峰填谷
当请求数量突增时,有可能超过系统的处理能力时,如果拒绝消息会造成系统接下来一段时间的空闲,可以将其积压在MQ中(削峰),在高发时期过后(积压的消息还未消费完时),系统可以去消费积压在MQ中的消息(填谷),使系统的并发数量保持在较高水平,可以提高系统的稳定性。
- 劣势
系统在添加了MQ之后,业务全部依赖于MQ,一旦MQ宕机或者出现其他故障,就会对业务造成影响(降低了系统的稳定性),因此如何保证MQ的高可用、如何保证消息的不丢失也是一大问题。
- 如何保证MQ的高可用
- 如何保证消息不丢失
- 常见的 MQ 产品
1.安装rabbitmq
待补充。
2.快速入门
RabbitMQ中的相关概念:
Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的
namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等(可以浅显的理解为mysql中不同的database)
Connection:publisher/consumer 和 broker 之间的 TCP 连接
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
2.1简单模式
<dependencies>
//rabbitmq客户端依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>
// 创建rabbitmq工具类,发送者发送消息之前,用于建立与rabbitmq服务器之间的链接。
public class RabbitUtils {
private static ConnectionFactory connectionFactory = new ConnectionFactory();
static {
// rabbitmq的服务器地址
connectionFactory.setHost("127.0.0.1");
// 5672是RabbitMQ的默认端口号
connectionFactory.setPort(5672);
// 登录用户的名称
connectionFactory.setUsername("guest");
// 登录用户的密码
connectionFactory.setPassword("guest");
// VirtualHost(“选择数据库:可以理解为mysql使用表格之前需要先创建数据库。”)
connectionFactory.setVirtualHost("/test");
}
public static Connection getConnection() {
Connection conn = null;
try {
conn = connectionFactory.newConnection();
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
//创建生产者(简单模式)
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//获取TCP长连接
Connection conn = RabbitUtils.getConnection();
//创建通信“通道”,相当于TCP中的虚拟连接
Channel channel = conn.createChannel();
//创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
//第一个参数:队列名称ID
//第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
//第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
//第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
//其他额外的参数, null
channel.queueDeclare("helloworld",false, false, false, null);
String message = "firstMessage";
//四个参数
//exchange 交换机,暂时用不到,在后面进行发布订阅时才会用到
//队列名称
//额外的设置属性
//最后一个参数是要传递的消息字节数组
channel.basicPublish("", RabbitConstant.QUEUE_HELLOWORLD, null,message.getBytes());
channel.close();
conn.close();
System.out.println("===发送成功===");
}
}
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
Connection conn = RabbitUtils.getConnection();
Channel channel = conn.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD,false, false, false, null);
//从MQ服务器中获取数据
//创建一个消息消费者
//第一个参数:队列名
//第二个参数代表是否自动确认收到消息,false代表手动编程来确认消息,这是MQ的推荐做法
//第三个参数要传入DefaultConsumer的实现类()
channel.basicConsume("helloworld", false, new Reciver(channel));
}
}
class Reciver extends DefaultConsumer {
private Channel channel;
//重写构造函数,Channel通道对象需要从外层传入,在handleDelivery中要用到
public Reciver(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body);
System.out.println("消费者接收到的消息:"+message);
System.out.println("消息的TagId:"+envelope.getDeliveryTag());
//false只确认签收当前的消息,设置为true的时候则代表签收该消费者所有未签收的消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
实现效果:
这里需要注意:生产者在发送消息之后channel.close();conn.close();关闭了通道链接和客户端链接。而消费者不关闭。
持续更新中
2.2工作模式
- 工作模式
- 订阅模式
- 路由模式
- 通配符模式
- 消息确认机制