前言:最近在公司用到了activeMq接收消息,从网上查了些资料,形成了一个大概的理解,所以总结一下,因为知识都是网上学习的难免有疏漏之处,望理解。
参考:
https://blog.csdn.net/qwqw3333333/article/details/89337277
https://www.jianshu.com/p/cd8e037e11ff
https://www.jianshu.com/p/2b7dff270cfd
https://blog.csdn.net/weixin_40904369/article/details/80403601
https://www.cnblogs.com/bohexiongmao/p/9699778.html
https://blog.csdn.net/chy19911123/article/details/80457904
https://blog.csdn.net/joy1211/article/details/79776733
目录
第一章 ActiveMQ简介
ActiveMQ是Apache提供的开源的消息系统,纯Java实现,因此很好的支持J2EE提出的JMS规范。
主要作用:消息发送异步进行,无须等待接受者立即响应。
使用场景:异步处理、应用解耦、流量削峰、日志处理、消息通讯
消息通讯流程简图(大概扫一下就好,可以熟悉了再翻回来看):
更详细一点的,最下面是两种不同的接收方式,左边是同步接收,右边是异步接收:
第二章 JMS简介
在进行ActiveMQ进行编程的时候,经常会用到jms包下的类,如下面所示的类,于是了解了一下jms的作用。
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
2.1 JMS简介
JMS(Java Message Service,即Java消息服务)是一组Java应用程序接口和相应的语法,类似于JDBC,是一组与厂商无关的API,使Java程序能与不同的厂商消息组件进行通信。也就是说JMS是一个用于提供消息服务的技术规范,它制定了在整个消息服务提供过程中的所有数据结构和交互流程。
比如mysql给出了jdbc的实现,ActiveMQ给出了JMS的实现。
2.2 JMS消息类型与相关类
消息是 JMS 中的一种类型对象,由两部分组成:报头和消息主体。报头由路由信息以及有关该消息的元数据组成。消息主体则携带着应用程序的数据或有效负载。
根据有效负载的类型来划分,可以将消息分为几种类型,它们分别携带:
简单文本(TextMessage)、可序列化的对象 (ObjectMessage)、属性集合 (MapMessage)、字节流 (BytesMessage)、原始值流 (StreamMessage),还有无有效负载的消息 (Message)。
JMS相关类:
-
连接工厂。连接工厂(ConnectionFactory)是由管理员创建,并绑定到JNDI树中。客户端使用JNDI查找连接工厂,然后利用连接工厂创建一个JMS连接。
-
JMS连接。JMS连接(Connection)表示JMS客户端和服务器端之间的一个活动的连接,是由客户端通过调用连接工厂的方法建立的。
-
JMS会话。JMS会话(Session)表示JMS客户与JMS服务器之间的会话状态。JMS会话建立在JMS连接上,表示客户与服务器之间的一个会话线程。
-
JMS目的。JMS目的(Destination),又称为消息队列,是实际的消息源。
-
JMS生产者和消费者。生产者(Message Producer)和消费者(Message Consumer)对象由Session对象创建,用于发送和接收消息
步骤概要(概要代码在2.5):
生产者:
第一步:创建ConnectionFactory对象,需要指定服务端ip及端口号。
第二步:使用ConnectionFactory对象创建一个Connection对象。
第三步:开启连接,调用Connection对象的start方法。
第四步:使用Connection对象创建一个Session对象。
第五步:使用Session对象创建一个Destination对象(topic、queue),此处创建一个Queue对象。
第六步:使用Session对象创建一个Producer对象。
第七步:创建一个Message对象,创建一个TextMessage对象。
第八步:使用Producer对象发送消息。
第九步:关闭资源。
消费者:
消费者:接收消息。
第一步:创建一个ConnectionFactory对象。
第二步:从ConnectionFactory对象中获得一个Connection对象。
第三步:开启连接。调用Connection对象的start方法。
第四步:使用Connection对象创建一个Session对象。
第五步:使用Session对象创建一个Destination对象。和发送端保持一致queue,并且队列的名称一致。
第六步:使用Session对象创建一个Consumer对象。
第七步:接收消息。
第八步:关闭资源
2.3 JMS消息传递模型
2.3.1 点对点模式
消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。
消息被消费以后(消费者ack应答确认/事务模式),queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。
Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费、其它的则不能消费此消息了,即只有一个consumer能消费此消息。
当消费者不存在时,消息会一直保存,直到有消费消费
2.3.2 订阅模式
消息生产者(发布)将消息发布到Topic中,同时有多个消息消费者(订阅该Topic)消费该消息。
和点对点方式不同,发布到topic的消息会被所有订阅者消费。
当生产者发布消息,不管是否有消费者。都不会保存消息。如果生产者向队列发送消息时,没有消费者订阅该队列,则消息全部丢失。否则向所有订阅了该Topic的消费者发送同样的消息(即:消费者必须在线)
2.4 JMS的接收方式
在JMS中,消息的产生和消费都是异步的。对于消费来说,JMS的消息者可以通过两种方式来消费消息。
(1)同步:订阅者或接收者通过receive方法来接收消息,receive方法在接收到消息之前(或超时之前)将一直阻塞;
/* 创建消息消费者*/
messageConsumer = session.createConsumer(destination);
Message message;
while ((message = messageConsumer.receive()) != null) {
System.out.println(((TextMessage) message).getText());
}
(2)异步:订阅者或接收者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的onMessage方法。
/* 创建消息消费者*/
messageConsumer = session.createConsumer(destination);
/* 设置消费者监听器,监听消息*/
messageConsumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
try {
System.out.println(((TextMessage) message).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
2.5 JMS实践
点对点模式:
/**
* 消息生产者
*/
public class Producer {
public static void main(String[] args) throws InterruptedException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
MQProperties.IP
);
try {
//通过 connection创建连接
Connection connection=connectionFactory.createConnection();
connection.start();
//通过连接创建 session
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Destination destination= session.createQueue(MQProperties.QUEUE_NAME);
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
for (int i = 0; i < 10; i++) {
ObjectMessage message = session.createObjectMessage("QUEUE计数器:【"+i+"】");
producer.send(message);
Thread.sleep(1000);
session.commit();
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
/**
* 消息消费者
*/
public class Consumer {
public static void main(String[] args) {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
MQProperties.IP
);
Connection connection;
try {
connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue(MQProperties.QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(destination);
while (true) {
ObjectMessage message = (ObjectMessage) consumer.receive(10000);
if (null != message) {
System.out.println(message.getObject().toString());
}
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
订阅模式:
/**
* 消息发布者
*/
public class Publisher {
public static void main(String[] args) throws InterruptedException {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
MQProperties.IP
);
try {
//通过 connection创建连接
Connection connection=connectionFactory.createConnection();
connection.start();
//通过连接创建 session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(MQProperties.TOPIC_NAME);
MessageProducer producer = session.createProducer(topic);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
for (int i = 0; i < 10; i++) {
TextMessage message = session.createTextMessage();
message.setText("TOPIC计数器:【"+i+"】");
producer.send(message);
Thread.sleep(1000);
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
/**
* 消息订阅者
*/
public class Subscriber {
public static void main(String[] args) {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
MQProperties.IP
);
Connection connection;
try {
connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(MQProperties.TOPIC_NAME);
MessageConsumer consumer = session.createConsumer(topic);
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage tm = (TextMessage) message;
try {
System.out.println(tm.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
} catch (JMSException e) {
e.printStackTrace();
}
}
}
2.6 扩展
ActiveMQ基于JMS API开发的。
RabbitMQ是基于AMQP开发的。
AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。意味着我们可以使用Java的AMQP provider,同时使用一个python的producer加一个rubby的consumer。从这一点看,AQMP可以用http来进行类比,不关心实现的语言,只要大家都按照相应的数据格式去发送报文请求,不同语言的client均可以和不同语言的server链接。
Kafka提供了类JMS的特性,但在设计实现上并不遵循JMS规范。
第三章 ActiveMQ的信息交换模型
通过上面对JMS的分析,知道了JMS有两种信息交传递模型,点对点和发布/订阅模式,因为ActiveMQ实现了JMS接口,所以ActiveMQ也有这两种信息传递模型。
除了这两种模式,ActiveMQ还有种信息交换模型,即虚拟主题。
假设我们有三个消费者A、B、C都订阅了Z主题,每次生产者发消息三个消费者都会接收到,突然有一天,B消费者想要进行负载均衡或者为了防止挂掉,要增加一个消费者D,每次Z主题发布的消息由A,C,B与D中的一个接收,这时,JMS原本的功能就不够用了,JMS要不就是订阅模式的消费者全部接收,要不就是点对点模式的所有消费者只接收一个,不能做到让A与C都收到,B与D其中一个收到,这时就用到了虚拟主题。
对于消息发布者来说,就是一个正常的Topic,名称以VirtualTopic.开头。例如VirtualTopic.TEST。
对于消息接收端来说,是个队列,不同应用里使用不同的前缀作为队列的名称,即可表明自己的身份即可实现消费端应用分组。例如Consumer.A.VirtualTopic.TEST,说明它是名称为A的消费端,同理Consumer.C.VirtualTopic.TEST说明是一个名称为C的客户端。可以在同一个应用里使用多个consumer消费此queue,则可以实现上面两个功能。又因为不同应用使用的queue名称不同(前缀不同),所以不同的应用中都可以接收到全部的消息。每个客户端相当于一个持久订阅者,而且这个客户端可以使用多个消费者共同来承担消费任务。
针对上面的情况,我们就可以
消息发布者的Topic:
VirtualTopic.TEST
消息消费者的Topic:
A消费者:Consumer.A.VirtualTopic.TEST
C消费者:Consumer.C.VirtualTopic.TEST
B消费者:Consumer.BD.VirtualTopic.TEST
D消费者:Consumer.BD.VirtualTopic.TEST
这样就能做到A,C都接到消息,B,D每次一个接到消息。
第四章 ActiveMQ结合SpringBoot
因为项目是现成的,我就根据推断直接复制过来了,下面只有消费者的配置,因为这篇主要是说概念,代码主要是提供个思路,参考一下就好,可能有冗余的地方,想要具体代码的建议去找网上一些其它的帖子。
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
消费者配置:
监听器配置:
@Component
public class HouyiMessageListener implements SessionAwareMessageListener {
private Logger log = LoggerFactory.getLogger(HouyiMessageListener.class);
@Override
public void onMessage(Message message, Session session) {
String result ="";
try {
ActiveMQTextMessage mes = (ActiveMQTextMessage) message;
result = mes.getText();
log.info("接收到消息"+result);
/*
处理消息
*/
message.acknowledge();
} catch (Exception e) {
log.info("消息保存错误{},等待重发",result);
try {
session.recover();
} catch (JMSException e1) {
log.info("消息重发失败{}",result);
}
}
}
}
系统属性配置:
@Configuration
public class MqConfig {
@Autowired
private HouyiMessageListener houyiMessageListener;
@Value("${activemq.url}")
private String url;
@Value("${activemq.topic}")
private String topic;
@Value("${activemq.userName}")
private String userName;
@Value("${activemq.password}")
private String password;
@Value("${activemq.sendTimeout}")
private String sendTimeout;
@Value("${activemq.sessionCacheSize}")
private String sessionCacheSize;
private ActiveMQConnectionFactory activeMQConnectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL(url);
activeMQConnectionFactory.setUserName(userName);
activeMQConnectionFactory.setPassword(password);
activeMQConnectionFactory.setUseAsyncSend(true);
activeMQConnectionFactory.setSendTimeout(Integer.parseInt(sendTimeout));
activeMQConnectionFactory.setTrustAllPackages(true);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy());
return activeMQConnectionFactory;
}
@Bean
public CachingConnectionFactory cachingConnectionFactory() {
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
cachingConnectionFactory.setTargetConnectionFactory(activeMQConnectionFactory());
cachingConnectionFactory.setSessionCacheSize(Integer.parseInt(sessionCacheSize));
return cachingConnectionFactory;
}
public Destination destination() {
ActiveMQQueue activeMQQueue = new ActiveMQQueue();
activeMQQueue.setPhysicalName(topic);
return activeMQQueue;
}
@Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(cachingConnectionFactory());
jmsTemplate.setExplicitQosEnabled(true);
jmsTemplate.setDeliveryPersistent(true);
jmsTemplate.setDefaultDestination(destination());
return jmsTemplate;
}
@Bean
public DefaultMessageListenerContainer defaultMessageListenerContainer() {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setMessageListener(houyiMessageListener);
messageListenerContainer.setSessionAcknowledgeMode(4);
messageListenerContainer.setDestination(destination());
messageListenerContainer.setConnectionFactory(activeMQConnectionFactory());
//关闭事物,否则消息重发不生效
messageListenerContainer.setSessionTransacted(false);
return messageListenerContainer;
}
/**
* 消息失败重发
**/
@Bean
public RedeliveryPolicy redeliveryPolicy(){
RedeliveryPolicy redeliveryPolicy= new RedeliveryPolicy();
//是否在每次尝试重新发送失败后,增长这个等待时间
redeliveryPolicy.setUseExponentialBackOff(true);
//重发次数,默认为6次 这里设置为10次
redeliveryPolicy.setMaximumRedeliveries(10);
//重发时间间隔,默认为1秒
redeliveryPolicy.setInitialRedeliveryDelay(1);
//第一次失败后重新发送之前等待500毫秒,第二次失败再等待500 * 2毫秒,这里的2就是value
redeliveryPolicy.setBackOffMultiplier(2);
//是否避免消息碰撞
redeliveryPolicy.setUseCollisionAvoidance(false);
//设置重发最大拖延时间-1 表示没有拖延只有UseExponentialBackOff(true)为true时生效
redeliveryPolicy.setMaximumRedeliveryDelay(-1);
return redeliveryPolicy;
}
}
第五章 ActiveMQ与其它MQ的区别
从网上随便复制了一个过来,等以后熟悉了别的或者有空了再来详细写写。