消息队列(MQ)

1.写在前面       

        本人是一枚编程小白,该文章内容是经过大量的资料翻阅,以本人的理解进行一个讲解。我会尽可能的说的很明白,如果您认为我理解的有问题,欢迎评论区留言,万分感谢。

2.什么是消息队列?

        消息队列也就是我们说的MQ,他是一个可独立部署的中间件产品。消息,顾名思义就是发送出来的信息,队列,大家学过一点数据结构应该就会很清楚队列是什么,在这里给大家打一个比方。一根管子你从管子的一头放乒乓球,先放进去的一定是先从另一头出来的,所以队列有个特点是“先进先出”。简单来说:系统A发送消息给MQ,系统B再从MQ中读取消息。

3.消息队列有什么作用?

解耦,异步,削峰

这三个作用大家如果想要详细了解,可以直接去查一下,网上资源很多。为了方便大家之后的理解我在这里简单说明一下。

解耦:

        比如:现在系统A产生了一条数据需要推送给系统B,我可以直接远程调用系统B的接口,将这条数据推送给系统B。但是现在系统C也需要这条数据,然后我再远程调用系统C的接口,将这条数据推送给他。现在系统D也有这个需求了...大家有没有发现这样是不是很麻烦,因为以后可能还有很多系统需要这条数据,你就得不断地去更新升级代码。

        现在,有了MQ我们可以怎么做,系统A产生数据后发送给MQ,刚才也说了MQ是一个中间件,系统B,系统C可以直接从MQ中取这条数据,包括以后有新的系统需要这条数据,系统A也不需要做更新升级。

异步:

        比如:系统A在推送这条数据的时候,必须先推给系统B,在推给系统C,在推给系统D,这样增加了耗时,增加了时间复杂度。可能大家会想到用线程去推送,因为线程可以并发的去推送,可以实现推送给B,C,D同步执行,但是这只是3系统,不保证后面会有很多系统有这种需求,因此这种方式会占用很多线程,增加了空间复杂度。(我不确定时间复杂度与空间复杂度的概念我应用的是否准确,欢迎大家留言。)

        现在,系统A把消息交给MQ之后他就可以继续往下执行了,其它系统去获取消息只需从MQ中拿就行了。在这边接着给大家讲一个概念,生产者和消费者。生产者就是产生消息的那一方,就是目前的系统A,消费者就是消费消息的一方就是系统B,C,D。

削峰:

        比如:系统A现在是一个电商系统,在双11的时候这种数据是很大的,一秒钟会有大量的数据去请求数据库进行写库,而数据库一定是有上限的,这个时候数据库就很容易崩,而且崩了会很麻烦。

        现在,系统A还是正常的操作,只不过他不进行写库操作,而是将数据通过消息发送给MQ,这些消息由系统B来慢慢进行消费,将写库的峰值变得平缓。以保证数据库不会一下子崩掉。

4.MQ选型

MQ包括:ActiveMQ、RocketMQ、RabbitMQ、Kafka等

        这些都称为MQ,至于他们之间的区别我就不给大家讲解了,大家随便一查资料就可以查到,而且,真正开发的时候选用什么MQ也不是你我说的算的,这是要经过谨慎的推敲才能定夺。

5.ActiveMQ的SpringBoot实现
  • 第一步:创建Maven工程

  • 第二步:引入配置(pom.xml)

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
  • 第三步:配置文件(application.yml)
amqConfig:
  amqUrl: tcp://127.0.0.1:61616
  user: admin
  pwd: admin
  • 第四步:两种方式

方式一:点对点的消息传递--队列(Queue)

特点:

1.每个消息只能有一个消费者,类似于1对1的关系。

2.无论消费者在生产者发送消息的时候是否处于运行状态,消费者都可以提取消息。

3.消息被消费后就销毁掉。

创建生产者和消费者:

public class ActiveMQUtil {

    // MQ连接
    @Value("${amqConfig.amqUrl}")
    public String url;
    // 用户名
    @Value("${amqConfig.use}")
	public String user;
    // 密码
    @Value("${amqConfig.pwd}")
	public String pwd;
	

/**
*发送消息
*queue:队列名称
*msg:发送内容
**/
public static void SendMsg(String queue,String msg) throws JMSException {
        
        // 1.创建连接工厂,传入url,用户名,密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(AMQ_USER, AMQ_PWD, AMQ_URL);

		// 2.创建connection连接并启动连接
		Connection connection = activeMQCommUtil.connectionFactory.createConnection();
		connection.start();
		// 3.创建Session(两个参数:transacted=是否开启事务,acknowledgeMode=签收模式)
		Session session = connection.createSession(Boolean.TRUE.booleanValue(), Session.AUTO_ACKNOWLEDGE);
        // 4.创建目的地(具体是队列(queue)还是主题(top))
		Destination destination = session.createQueue(queue);
		// 5.创建消息生产者
		MessageProducer producer = session.createProducer(destination);
        // 这里表示连接是非持久性的
		producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
		//6、创建消息并赋值
        TextMessage textMessage = session.createTextMessage();  //String类型的
        textMessage.setText(msg);
        //7、发送消息
		producer.send(textMessage);
		session.commit();

		// 关闭连接
		producer.close();
		session.close();
		connection.close();
	}

    /**
	 * 接收消息
	 * @param Queue 队列名称
	 * @throws JMSException
	 */
	public static TextMessage ReceiveMsg(String queue) throws JMSException {

        // 1.创建连接工厂,传入url,用户名,密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new         ActiveMQConnectionFactory(AMQ_USER, AMQ_PWD, AMQ_URL);

		// 2.创建connection连接并启动连接
		Connection connection = activeMQCommUtil.connectionFactory.createConnection();
		connection.start();
		// 3.创建Session(两个参数:transacted=是否开启事务,acknowledgeMode=签收模式)
		Session session = connection.createSession(Boolean.TRUE.booleanValue(), Session.CLIENT_ACKNOWLEDGE);
		Destination destination = session.createQueue(Queue);
		// 5.创建消息消费者
		MessageConsumer consumer = session.createConsumer(destination);   
        // 6.接收消息
		TextMessage msg = (TextMessage) consumer.receive();
		session.commit();

		// 7.关闭连接
		consumer.close();
		session.close();
		connection.close();

		return msg;

	}

}

下面对以上代码简单讲解:可以看到创建生产者和消费者代码基本相同,代码注释已经比较详细。我只对一点做出一下讲解,两个方法中都有一个共同的参数“队列名称”,假设没有这个名称,系统A发出两个消息标为1号和2号,系统B和系统C谁也不知道这个消息到底1号是B的还是2号是B的。那现在有这个名称了之后,比如1号消息在名称为“1号”的队列中,我系统B中也配置一个这样的名称,这样系统B再去取消息的时候就直接到1号队列中去取。

方式二:发布订阅消息传递域——主题(topic)

特点:

1、生产者将消息发布到topic中,每个消息可以有多个消费者,属于1:N的关系
2、生产者和消费者之间有时间上的相关性。订阅某一个主题的消费者只能消费自它订阅之后发布的消息
3、生产者生产时,topic不保存消息,假如无人订阅就去生产,那就是一条废消息,所以,一般先启动消费者再启动生产者

大家理解上面的哪个这个的实现和上面就相差不大了只不过这个创建的是topic,上面的是queue。我也简单写一下

public class ActiveMQUtil {

    // MQ连接
    @Value("${amqConfig.amqUrl}")
    public String url;
    // 用户名
    @Value("${amqConfig.use}")
	public String user;
    // 密码
    @Value("${amqConfig.pwd}")
	public String pwd;
	

/**
*发送消息
*topic:队列名称
*msg:发送内容
**/
public static void SendMsg(String topic,String msg) throws JMSException {
        
        // 1.创建连接工厂,传入url,用户名,密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(AMQ_USER, AMQ_PWD, AMQ_URL);

		// 2.创建connection连接并启动连接
		Connection connection = activeMQCommUtil.connectionFactory.createConnection();
		connection.start();
		// 3.创建Session(两个参数:transacted=是否开启事务,acknowledgeMode=签收模式)
		Session session = connection.createSession(Boolean.TRUE.booleanValue(), Session.AUTO_ACKNOWLEDGE);
        // 4.创建目的地(具体是队列(queue)还是主题(top))
		Destination destination = session.createTopic(topic);
		// 5.创建消息生产者
		MessageProducer producer = session.createProducer(destination);
        // 这里表示连接是非持久性的
		producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
		//6、创建消息并赋值
        TextMessage textMessage = session.createTextMessage();  //String类型的
        textMessage.setText(msg);
        //7、发送消息
		producer.send(textMessage);
		session.commit();

		// 关闭连接
		producer.close();
		session.close();
		connection.close();
	}

    /**
	 * 接收消息
	 * @param topic 队列名称
	 * @throws JMSException
	 */
	public static TextMessage ReceiveMsg(String topic) throws JMSException {

        // 1.创建连接工厂,传入url,用户名,密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new         ActiveMQConnectionFactory(AMQ_USER, AMQ_PWD, AMQ_URL);

		// 2.创建connection连接并启动连接
		Connection connection = activeMQCommUtil.connectionFactory.createConnection();
		connection.start();
		// 3.创建Session(两个参数:transacted=是否开启事务,acknowledgeMode=签收模式)
		Session session = connection.createSession(Boolean.TRUE.booleanValue(), Session.CLIENT_ACKNOWLEDGE);
		Destination destination = session.createTopic(topic);
		// 5.创建消息消费者
		MessageConsumer consumer = session.createConsumer(destination);   
        // 6.接收消息
		TextMessage msg = (TextMessage) consumer.receive();
		session.commit();

		// 7.关闭连接
		consumer.close();
		session.close();
		connection.close();

		return msg;

	}

}

=========================================================================

通过两种方式的特点,大家根据实际情况权衡使用。

注意:发送和接收的消息类型要一致。都有哪些消息格式?

        1.TxtMessage : 普通字符串消息,包含一个String

        2.MapMessage :一个Map类型的消息,key为Strng类型,而值为Java基本类型

        3.BytesMessage : 二进制数组消息,包含一个byte[]

        4.StreamMessage : Java数据流消息,用标准流操作来顺序填充和读取

        5.ObjectMessage :对象消息,包含一个可序列化的Java对象

========================================================================

        关于ActiveMQ,就讲这么多了如果还不明白,大家可以去看一下这个作者讲的,讲的很不错的。

ActiveMQ详细使用(含高级篇)_activemq使用方法-优快云博客

========================================================================

        其他MQ:RocketMQ、RabbitMQ、Kafka等,留到后面讲解,因为目前我还没有真正在项目中用过,只是停留在练习阶段,这些其实都大差不差。

        好了,就到这了。欢迎大家点赞,评论,关注。欢迎指出文章中的错误,万分感谢。

=========================stay hungry stay foolish===========================

### 关于消息队列 MQ 的常见面试题及其解答 #### 1. 生产者丢失消息的原因及解决方案 生产者可能会因为网络传输中的丢包或者消息队列服务端发生异常未能成功接收消息而导致消息丢失。为了防止这种情况的发生,主流的消息队列产品通常提供了确认机制或事务支持功能来保障消息能够被可靠地传递至消息队列服务器。例如,在 RabbitMQ 中存在两种主要方式用于确保消息不会丢失——事务模式和 Confirm 模式[^1]。 #### 2. 如何保证消息的顺序性? 对于某些业务场景而言,保持消息按特定次序处理是非常重要的需求之一。RabbitMQ 可通过配置单线程消费者以及设置 `prefetch_count=1` 来实现这一目标。这意味着每次只允许一个未完成的任务分配给某个具体的消费者实例直到前一任务结束为止从而维持全局有序性[^3]。 #### 3. 部署复杂度与运维挑战 随着企业规模扩大和技术架构日益复杂化, 对应地也提高了对开发人员特别是 DevOps 工程师们的技术水平要求。他们不仅需要熟悉各种开源工具链如 Docker/Kubernetes 等容器编排平台的知识点外还需掌握诸如 Kafka/Zookeeper 或 Pulsar/BookKeeper 类型分布式流计算框架的工作原理以便更好地管理和维护整个系统生命周期内的稳定性表现良好性能指标达成预期效果满足实际应用场景下的多样化需求特点[^2]. #### 4. 数据库写入与缓存更新的一致性问题 当应用程序既涉及到了数据库记录修改又包含了关联对象存储(比如Redis)状态刷新时,则很容易遇到因时间差造成两者间短暂不同步的现象。一种可行的办法就是在执行SQL语句之前先把对应变动通知推送到中间件上等待后续由专门负责监听该主题订阅者的回调函数再去主动触发相应的动作以此达到最终一致性的目的尽管在此过程中可能存在短暂时延使得部分查询请求返回陈旧版本的信息内容但是总体来说这种方法兼顾了吞吐量提升的同时又能较好控制风险范围不至于影响整体服务质量水准下降太多程度范围内可接受的程度之内[^4]。 ```python import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() def update_cache(message): # Simulate cache updating logic here. pass def on_message(ch, method, properties, body): message = json.loads(body.decode()) try: with database_transaction(): process_database_update(message) ch.basic_ack(delivery_tag=method.delivery_tag) update_cache(message) except Exception as e: logging.error(f"Failed to handle message {message}: {e}") ch.basic_nack(delivery_tag=method.delivery_tag) channel.basic_consume(queue='task_queue', on_message_callback=on_message) channel.start_consuming() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值