ActiveMQ入门(一)
1. 为什么要学习MQ
因为我们在发送接收消息时,如果采用同步的话,一个个等待效率十分低下,但是如果一下子蜂拥而至,对于高并发的情况处理是削峰防止崩溃,更重要的是解耦,避免了RPC式同步调用等待
以上的原因导致了MQ消息中间件的出现,用异步的方式,以及削峰手段,还有良好的解耦性,使项目更对于消息的处理更为完善
2. MQ的作用
A不用和B有强耦合关系,只需要通过消息队列确认收发
而且他们不用同时在线,收发异步,很方便就像qq一样
官网下载:传送门
-
传到/opt目录下解压 tar -zxvf 名字
-
cp -r apache-activemq-5.15.12 /myactiveMQ/
-
复制到自己的文件夹
-
进入bin目录然后 ./activemq start启动
发现没有启动起来 ./activemq status查看状态
下面这个博客解决了无法启动的一个问题
https://blog.csdn.net/xiewenfeng520/article/details/85236304?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1
启动成功
查看tomcat也启动了
netstat -anp|grep 61615查看端口使用情况
./activemq stop 停止运行
带日志的启动
./activemq start > /myactiveMQ/日志文件名
可以去vim这个log看日志
3.控制台查看
报错访问不到之后查看自己hostname是否是绑定了127.0.0.1 可以通过ping 主机名成功就是绑定了
史前踩坑
我用的阿里云服务器,发现安全组规则没有打开,后来用ip:8161一直不行,打开了端口还是不行,后来我去宝塔面板放行了8161,才明白过来,原来是我之前域名绑定过,并且解析了,基本用宝塔放行后我用域名:8161就可以成功了,
留个图纪念一下
点击broker登录,账号密码都是admin
4.创建Maven项目
pom依赖
<dependencies>
<!-- activemq所需要的jar-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.12</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12}</version>
</dependency>
</dependencies>
4.1 JMS总体规范
和jdbc编码有点像
Destination:发送消息到的目的地,就比如new book(),这个会去堆里面一样,消息也会去目的地,里面有队列和主题
一对一:去私聊 目的地队列
一对多:微信推送公众号 目的地主题
4.2 生产者入消息队列代码
package com.example;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce {
public static final String URL="tcp://www.cqfbest.ltd:61615";
public static final String Queue_name="queue01";
public static void main(String[] args) {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(URL);
//2.通过连接工厂,获得连接connection
try {
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
//3.创建Session 两个参数:事务和签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);//先默认
//4。创建目的地(队列或者主题)
Queue queue = session.createQueue(Queue_name);
Destination destination= session.createQueue(Queue_name);
//5.创建消息的生产者
MessageProducer producer = session.createProducer(queue);
//6.通过使用消息生产者生产3条消息到队列里面
for (int i = 0; i < 3; i++) {
//7.创建消息
TextMessage textMessage = session.createTextMessage("msg---" + (i + 1));//一个消息字符串
//8. 通过生产者发送给MQ
producer.send(textMessage);
//9.倒着关闭
}
producer.close();
session.close();
connection.close();
System.out.println("发送完成!");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
辅助识记
4.3 消费者消费队列消息代码
package com.example;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsConsumer {
public static final String URL="tcp://www.cqfbest.ltd:61615";
public static final String Queue_name="queue01";
public static void main(String[] args) {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(URL);
//2.通过连接工厂,获得连接connection
try {
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
//3.创建Session 两个参数:事务和签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);//先默认
//4。创建目的地(队列或者主题)
Queue queue = session.createQueue(Queue_name);
Destination destination= session.createQueue(Queue_name);
//5.创建消费者
MessageConsumer consumer = session.createConsumer(queue);
//6.通过使用消息生产者生产3条消息到队列里面
while (true){
//7.接收消息 类型一样和生产的消息格式
TextMessage textMessage = (TextMessage) consumer.receive();
if(textMessage!=null){
System.out.println("消费者接收到消息:"+textMessage.getText());
}else {
break;
}
}
//8.倒着关闭
consumer.close();
session.close();
connection.close();
System.out.println("接收完毕!");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
打印出来的消息
recive(4000L); 4秒以后没收到消息,消费者自己退出
4.3.1 利用监听器的消费者
接口监听
public interface MessageListener {
void onMessage(Message message);
}
package com.example;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import javax.jms.MessageListener;
import java.io.IOException;
public class JmsConsumer2 {
public static final String URL="tcp://www.cqfbest.ltd:61615";
public static final String Queue_name="queue01";
public static void main(String[] args) throws IOException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(URL);
//2.通过连接工厂,获得连接connection
try {
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
//3.创建Session 两个参数:事务和签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);//先默认
//4。创建目的地(队列或者主题)
Queue queue = session.createQueue(Queue_name);
Destination destination= session.createQueue(Queue_name);
//5.创建消费者
MessageConsumer consumer = session.createConsumer(queue);
//6.监听消息MessageListener
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
if(null!=message&& message instanceof TextMessage){
TextMessage textMessage= (TextMessage) message;
try {
System.out.println("消费者接收到消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read(); //保证控制台不灭 doubbo源码有
consumer.close();
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
一样的效果但是这个需要后面有read()缓冲时间再关闭
4.3.2 总结流程
5. 消费者的几种情况
- 一个生产者然后开启两个消费者,1号消费完,2号能消费吗?2号消费者消费不到,因为1号直接全部接收完了消息
- 一个生产者对应一个消费者,最好的情况
- 先启动2个消费者,然后再生产6条消息,结果怎么样? 答案:有点像轮询 1号接收1,3,5,二号接收2,4,6
6.生产者发布消息主题
主题是不保存消息的,发布出来无人订阅都浪费资源空间了,=所以一般先启动消费者后启动生产者
和队列一样,就改queue为topic
package com.example.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import static com.example.JmsProduce.Queue_name;
public class JmsProduce {
public static final String URL="tcp://www.cqfbest.ltd:61615";
public static final String topic_name="topic01";
public static void main(String[] args) {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(URL);
//2.通过连接工厂,获得连接connection
try {
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
//3.创建Session 两个参数:事务和签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);//先默认
//4。创建目的地(队列或者主题)
Topic topic = session.createTopic(topic_name);
Destination destination= session.createTopic(topic_name);
//5.创建消息的生产者
MessageProducer producer = session.createProducer(topic);
//6.通过使用消息生产者生产3条消息到队列里面
for (int i = 0; i < 3; i++) {
//7.创建消息
TextMessage textMessage = session.createTextMessage("topic消息---" + (i + 1));//一个消息字符串
//8. 通过生产者发送给MQ
producer.send(textMessage);
//9.倒着关闭
}
producer.close();
session.close();
connection.close();
System.out.println("主题消息发送完成!");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
7. 消费者订阅消息主题
package com.example.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.MessageListener;
import javax.jms.*;
import java.io.IOException;
public class JmsConsumer2 {
public static final String URL="tcp://www.cqfbest.ltd:61615";
public static final String Topic_name="topic01";
public static void main(String[] args) throws IOException {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(URL);
//2.通过连接工厂,获得连接connection
try {
Connection connection=activeMQConnectionFactory.createConnection();
connection.start();
//3.创建Session 两个参数:事务和签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);//先默认
//4。创建目的地(队列或者主题)
Topic topic = session.createTopic(Topic_name);
Destination destination= session.createTopic(Topic_name);
//5.创建消费者
MessageConsumer consumer = session.createConsumer(topic);
//6.监听消息MessageListener
// consumer.setMessageListener(new MessageListener() {
// public void onMessage(Message message) {
// if(null!=message&& message instanceof TextMessage){
// TextMessage textMessage= (TextMessage) message;
// try {
// System.out.println("消费者接收到消息:"+textMessage.getText());
// } catch (JMSException e) {
// e.printStackTrace();
// }
// }
// }
// });
consumer.setMessageListener((Message message)->{
if(null!=message&& message instanceof TextMessage){
TextMessage textMessage= (TextMessage) message;
try {
System.out.println("消费者接收到消息:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.in.read(); //保证控制台不灭 doubbo源码有
consumer.close();
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
接口的话和队列里写的那个一样,拿来复用
启动完生产者会发现每个消费者全收到了,不像队列一样负载均衡
试试先启动生产发布者,再启动订阅者
再生产的话,消费者可以接收,之前先生产的是3条废消息
7.1 两大模式比较
JMS到底是什么?
JMS是JAVAEE的一个子模块
两个程序之间异步通信的API,定义了消息的规范
消息的可靠性之持久化和非持久化
消息不持久,服务崩了不会保存消息,持久化后可以保存,再发送一次
队列默认是持久的,服务崩掉再开启也是可以再接收消息的,它保证接收和传送都一次
1.持久化发布订阅
package com.example.db_topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class JmsConsumer1 {
public static final String URL="tcp://www.cqfbest.ltd:61615";
public static final String Topic_name="topic01";
public static void main(String[] args) throws IOException {
System.out.println("我是一号消费者");
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(URL);
//2.通过连接工厂,获得连接connection
try {
Connection connection=activeMQConnectionFactory.createConnection();
connection.setClientID("z3");
//3.创建Session 两个参数:事务和签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);//先默认
//4。创建目的地(队列或者主题)
Topic topic = session.createTopic(Topic_name);
TopicSubscriber topicSubscriber=session.createDurableSubscriber(topic,"remark...");
connection.start();
Message message=topicSubscriber.receive();
while (null!=message){
TextMessage textMessage= (TextMessage) message;
System.out.println("收到的持久化topic:"+textMessage.getText());
message=topicSubscriber.receive(5000L);
}
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
package com.example.db_topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class JmsProduce {
public static final String URL="tcp://www.cqfbest.ltd:61615";
public static final String topic_name="topic01";
public static void main(String[] args) {
//1.创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(URL);
//2.通过连接工厂,获得连接connection
try {
Connection connection=activeMQConnectionFactory.createConnection();
//3.创建Session 两个参数:事务和签收
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);//先默认
//4。创建目的地(队列或者主题)
Topic topic = session.createTopic(topic_name);
Destination destination= session.createTopic(topic_name);
//5.创建消息的生产者
MessageProducer producer = session.createProducer(topic);
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
//6.通过使用消息生产者生产3条消息到队列里面
for (int i = 0; i < 3; i++) {
//7.创建消息
TextMessage textMessage = session.createTextMessage("topic消息---" + (i + 1));//一个消息字符串
//8. 通过生产者发送给MQ
producer.send(textMessage);
//9.倒着关闭
}
producer.close();
session.close();
connection.close();
System.out.println("主题消息发送完成!");
} catch (JMSException e) {
e.printStackTrace();
}
}
}
会一直等待主题发布消息,除非自己设置了超时取关
等消费者再重新连接依然收得到之前的消息
2. 事务
开启了事务,一定要commit提交
session.commit();
3.签收
上面讲的都是自动签收
手动签收
允许重复签收(不常用)
生产事务开启只有commit才能把消息转为已签收,事务权限大于签收
没有commit即使写了ack也没用,写了事务提交了即使没有ack也以消费
4. JavA代码操作Broker
复制一份activemq.xml
cp activemq.xml activemq02.xml
启动新的复制的xml
./activemq start xbean:file:/myactiveMQ/apache-activemq-5.15.12/conf/activemq02.xml
Broker是什么? 它就是嵌入java代码的形式启动mq,随时用随时启动
pom依赖
<!-- borker-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.3</version>
</dependency>
public class EmbedBorker {
public static void main(String[] args) throws Exception {
//嵌入式代码执行
BrokerService brokerService=new BrokerService();
brokerService.setUseJmx(true);
brokerService.addConnector("tcp://www.cqfbest.ltd:61615");
brokerService.start();
}
}
启动后,原来的生产者和消费者只要把地址改成本地localhost就可以,在本地收发消息,broker相当于启动new了一个消息中间件
5. springboot实现队列消息发送
第一步创建springbbot项目,导入activemq依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
5.1 ConfigBean.java
package com.example.config;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
@Component
@EnableJms //开启jms
public class ConfigBean {
@Value("${myqueue}")
private String myqueue;
@Bean
public Queue queue(){
return new ActiveMQQueue(myqueue);
}
}
5.2 Queue_Produce.java
package com.example.produce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
import java.util.UUID;
@Component
public class Queue_Produce {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue queue;
public void produceMsg(){
jmsMessagingTemplate.convertAndSend(queue,"***:"+ UUID.randomUUID().toString().substring(0,6));
}
}
5.3 application.yml
server:
port: 7777
spring:
activemq:
broker-url: tcp://www.cqfbest.ltd:61615
user: admin
password: admin
jms:
pub-sub-domain: false #默认false 队列 true 为topic
myqueue: boot-activemq-queue #自定义队列名字
5.4 Test.java
package com.example;
import com.example.produce.Queue_Produce;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class testActiveMQ {
@Resource
private Queue_Produce queue_produce;
@Test
public void testProduce(){
queue_produce.produceMsg();
}
}
上传成功
6.springboot实现队列消费者
application.yml还有pom文件都和生产者一样,没什么区别
7. springboot实现主题消息发布
7.1 ConfigBean.java
package com.example.config;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
import javax.jms.Topic;
@Component
@EnableJms //开启jms
public class ConfigBean {
@Value("${mytopic}")
private String mytopic;
@Bean
public Topic topic(){
return new ActiveMQTopic(mytopic);
}
}
7.2 Topic_Produce.java
package com.example.produce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
import javax.jms.Topic;
import java.util.UUID;
@Component
public class Topic_Produce {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Topic topic;
public void produceMsg(){
jmsMessagingTemplate.convertAndSend(topic,"***:"+ UUID.randomUUID().toString().substring(0,6));
}
//间隔时间3秒自动发消息
@Scheduled(fixedDelay = 3000)
public void prosend(){
jmsMessagingTemplate.convertAndSend(topic,"***:"+ UUID.randomUUID().toString().substring(0,6));
System.out.println("****定时3秒投递完成 send ok");
}
}
7.3 application.yml
server:
port: 6665
spring:
activemq:
broker-url: tcp://www.cqfbest.ltd:61615
user: admin
password: admin
jms:
pub-sub-domain: true #默认false 队列 true 为topic
mytopic: boot-activemq-topic #自定义队列名字
8springboot实现消费短信
package com.example.consumer;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.TextMessage;
@Component
public class Topic_Consumer {
@JmsListener(destination = "${mytopic}") //消息监听myqueue队列
public void ReciveMsg(TextMessage textMessage)throws Exception{
System.out.println("****丢丢发现手机收到了 "+textMessage.getText()+" 这样一条短信");
}
}
7.3 application.yml
server:
port: 6665
spring:
activemq:
broker-url: tcp://www.cqfbest.ltd:61615
user: admin
password: admin
jms:
pub-sub-domain: true #默认false 队列 true 为topic
mytopic: boot-activemq-topic #自定义队列名字
8springboot实现消费短信
package com.example.consumer;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.TextMessage;
@Component
public class Topic_Consumer {
@JmsListener(destination = "${mytopic}") //消息监听myqueue队列
public void ReciveMsg(TextMessage textMessage)throws Exception{
System.out.println("****丢丢发现手机收到了 "+textMessage.getText()+" 这样一条短信");
}
}