项目的DEMO代码:https://github.com/heyu52/-spring-cloud
消息队列中间件当下系统中无处不在,并且已经成为了系统中内部通信的核心手段。它有以下特点:
- 松耦合
- 异步消息
- 流量削峰
- 可靠投递
- 广播
- 流量控制
- 最终一致性
我使用最多的场景就是异步处理。
目前在业界使用比较多的消息队列有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ。这些消息队列都是现成的系统,我们安装即可使用。这一节我们就来讲讲ActiveMQ是如何使用的。
什么是JMS规范
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。
JMS 的消息机制有 2 种模型,一种是 Point to Point 即点对点(一对一),表现为队列的形式,发送的消息,只能被一个接收者取走;另一种是 Topic即发布订阅模式(一对多),可以被多个订阅者订阅,类似于群发。
ActiveMQ 介绍
ActiveMQ是Apache软件基金会所研发的开放源代码消息中间件;由于ActiveMQ是一个纯Java程序,因此只需要操作系统支持Java虚拟机,ActiveMQ便可执行。它支持Java,C,C++,C#,Python,Ruby,Perl。同时支持windows,linux。支持OpenWire、REST、STOMP、WS-Notification、MQTT、XMPP以及AMQP协议。我们之前所有的例子都是使用RESTFUL。它的官网地址如下:https://activemq.apache.org/
ActiveMQ 安装
目前ActiveMQ 的最新版本是5.5.9,下载地址为:https://activemq.apache.org/components/classic/download/
本打算使用Docker来进行部署的,但是官网上找不到官方的镜像,所以直接部署。我们直接下载linux的版本进行部署就可以了。
解压:tar -zxvf apache-activemq-5.15.9-bin.tar.gz
进入目录:cd bin
启动:./activemq start
看到以上就表示,ActiveMQ已启动了。
开启防火墙端口
1、如果使用了云服务器需要先开启8161(web管理页面端口)、61616(activemq服务监控端口) 两个端口
2、打开linux防火墙端口
/sbin/iptables -I INPUT -p tcp --dport 8161 -j ACCEPT&&/etc/init.d/iptables save&&service iptables restart&&/etc/init.d/iptables status
/sbin/iptables -I INPUT -p tcp --dport 61616 -j ACCEPT&&/etc/init.d/iptables save&&service iptables restart&&/etc/init.d/iptables status
后台管理:http://192.168.0.123:8161/
点击Manage ActiveMQ broker,会弹出登录,默认账号密码:admin/admin
ActiveMQ 是起来了,但有一个很现实的问题,它都现在都是存放在内存中,它并没有连接数据库,企业中没有人这么用的,真正用起来,必然是将消息都存到数据库中,要不然消息在系统故障时,就会产生丢失了。接下来,我们配置它与数据库相连。
1、下载数据库驱动包
因为我的mysql是8.0的版本,所以我下载了Mysql8.0的mysql-connector-java-8.0.16.jar包,我们将这个包复制到activemq的\lib\目录下。
2、下载 commons-dbcp2-2.1.1.jar,下载地址为:https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2/2.1.1
如果你的mysql是5.0的版本,那这个就不用,也同样复制到activemq的\lib\目录下。
3、修改activemq.xml
打开目录conf,找到activemq.xml进行编辑,修改persistenceAdapter
<persistenceAdapter>
<!-- <kahaDB directory="${activemq.data}/kahadb"/> -->
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>
增加bean
<!-- 用于持久化数据到Mysql数据库 -->
<bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.0.148:3306/activemq?serverTimezone=UTC&relaxAutoCommit=true"/>
<property name="username" value="root"/>
<property name="password" value="csdn"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
我们还要注意新增这个代码的位置,如下图
4、验证
为了验证启动是否有错,我们使用控制台的方式进行启动,打开终端,在bin 目录下执行
./activemq console
如果有报错,这里会有显示,再解决报错就好了。我们再查看数据库
数据库中自动新增了三个表。
我们再打开http://192.168.0.123:8161 ,正常显示。表明数据库方式已成功。
SpringBoot连接ActiveMQ
我们新建一个项目,选择webstart及activemq 5
项目创建后,注意一下pom文件,自动增加了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
创建队列与配置
package com.csdn.demo.Config;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;
@Configuration
public class MqConfig {
@Bean
ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL("tcp://192.168.0.123:61616");
connectionFactory.setUserName("admin");
connectionFactory.setPassword("admin");
return connectionFactory;
}
@Bean
public Queue queue()
{
return new ActiveMQQueue("csdn.queue");
}
}
消息生产者
package com.csdn.demo.MQProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
import javax.jms.Topic;
@Component
//消息的生产者
public class Producer {
//Spring 提供发送消息的工具类
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue query;
public void sendQueue(String msg){
System.out.println("send queue msg:"+msg);
this.jmsMessagingTemplate.convertAndSend(this.query,msg);
}
}
消息消费者
package com.csdn.demo.MQConsumer;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@JmsListener(destination = "csdn.queue")
public void receiveQueue(String text) {
System.out.println("Consumer queue msg : "+text);
}
}
创建测试Control
package com.csdn.demo.web;
import com.csdn.demo.MQProducer.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class myControl {
@Autowired
private Producer producer;
@RequestMapping("/MySendMethod")
public void MySendMethod() {
this.producer.sendQueue("MySendMethod queue message");
}
}
运行测试
编译运行项目后浏览器打开地址:http://127.0.0.1:8080/MySendMethod
查看ActiveMQ后台,同时会看到一个队列
接下来,我们注释消费者
@Component
public class Consumer {
// @JmsListener(destination = "csdn.queue")
public void receiveQueue(String text) {
System.out.println("Consumer queue msg : "+text);
}
}
编译运行项目后浏览器打开地址:http://127.0.0.1:8080/MySendMethod
打开数据库查看数据:
这里可以看到一条未处理的消息,同时后台管理界面也能看到
我们不再注释代码,重新运行项目,然后刷新数据库
同时看后台数据
消息已被处理了。
广播消息
我们来增加一个发广播消息的配置
@Bean
public Topic topic() {
return new ActiveMQTopic("csdn.queue");
}
增加一个生产者
@Autowired
private Topic topic;
public void sendTopic(String msg) {
System.out.println("发送广播消息 :"+msg);
this.jmsMessagingTemplate.convertAndSend(this.topic, msg);
}
处理广播消息
@JmsListener(destination = "csdn.topic")
public void receiveTopicB(String text) {
System.out.println("处理广播消息A : "+text);
}
@JmsListener(destination = "csdn.topic")
public void receiveTopicC(String text) {
System.out.println("处理广播消息B : "+text);
}
增加Controller方法
@RequestMapping("/MySendTopicMethod")
public void MySendTopicMethod() {
this.producer.sendTopic("MySendTopicMethod queue message");
}
运行项目,浏览器输入:http://127.0.0.1:8080/MySendTopicMethod
很不幸你只会发现,有发送,没接收
后台也没有待处理的消息
消息明显,丢失了。原因是Spring Boot 集成 ActiveMQ 的项目默认只支持队列或者广播中的一种,通过配置项 spring.jms.pub-subdomain 的值来控制,true 为广播模式,false 为队列模式,默认情况下支持队列模式。
我们增加配置
spring.jms.pub-sub-domain=true
运行项目测试
广播消息处理成功了。我们现在测试队列消息:http://127.0.0.1:8080/MySendMethod
此时,队列不处理了。
同时支持队列和广播
为了解决同时能处理两类消息,我们要增加配置
package com.neo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import javax.jms.ConnectionFactory;
@Configuration
@EnableJms
public class ActiveMQConfig {
@Bean("queueListenerFactory")
public JmsListenerContainerFactory<?> queueListenerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(false);
return factory;
}
@Bean("topicListenerFactory")
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
}
我们在处理消息时
@JmsListener(destination = "csdn.queue", containerFactory = "queueListenerFactory")
public void receiveQueueA(String text) {
System.out.println("处理队列消息A : "+text);
}
@JmsListener(destination = "csdn.topic", containerFactory = "topicListenerFactory")
public void receiveTopicD(String text) {
System.out.println("处理广播消息D : "+text);
}
运行项目
此时会发现,之前的队列消息,也一并被处理掉了。