在分布式系统的不同服务之间进行消息的发送和接收的一种技术方案。对于消息中间件,常见的角色大致也就有Producer(生产者)、Consumer(消费者)
在高并发环境下,MQ通过异步处理请求的方式极大的提高系统性能。
JMS(Java?Messaging Service)是Java平台上有关面向消息中间件的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口简化企业应用的开发。
· TextMessage–一个字符串对象
· MapMessage–一套名称-值对
· ObjectMessage–一个序列化的 Java 对象
· BytesMessage–一个字节的数据流
· StreamMessage – Java 原始值的数据流
1. p2p模式
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
public class QueueProducer {
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://127.0.0.1:61616");
//2.获取连接
Connection connection = connectionFactory.createConnection();
//3.启动连接
connection.start();
/*4.获取session (参数1:是否启动事务,
参数2:消息确认模式[
AUTO_ACKNOWLEDGE = 1 自动确认
CLIENT_ACKNOWLEDGE = 2 客户端手动确认
DUPS_OK_ACKNOWLEDGE = 3 自动批量确认
SESSION_TRANSACTED = 0 事务提交并确认
])*/
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.创建队列对象
Queue queue = session.createQueue("test-queue");
//6.创建消息生产者
MessageProducer producer = session.createProducer(queue);
//7.创建消息
TextMessage textMessage = session.createTextMessage("欢迎来到MQ世界");
//8.发送消息
producer.send(textMessage);
//9.关闭资源
producer.close();
session.close();
connection.close();
}
}
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class QueueConsumer {
public static void main(String[] args) throws IOException, JMSException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://127.0.0.1:61616");
//2.获取连接
Connection connection = connectionFactory.createConnection();
//3.启动连接
connection.start();
//4.获取session (参数1:是否启动事务,参数2:消息确认模式)
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.创建队列对象
Queue queue = session.createQueue("test-queue");
//6.创建消息消费者
MessageConsumer consumer = session.createConsumer(queue);
//7.监听消息
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("接收到消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
//8.等待键盘输入
System.in.read();
//9.关闭资源
consumer.close();
session.close();
connection.close();
}
}
启动生产者消费者就可以看到效果了!
2. topic模式
package MQ.activeMQ;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQTextMessage;
import javax.jms.*;
public class TopicProducer {
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("test-topic");
MessageProducer messageProducer = session.createProducer(topic);
TextMessage textMessage = new ActiveMQTextMessage();
textMessage.setText("欢迎使用topic模式的MQ");
messageProducer.send(textMessage);
messageProducer.close();
session.close();
}
}
package MQ.activeMQ;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
public class TopicConsumer1 {
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("test-topic");
MessageConsumer messageConsumer = session.createConsumer(topic);
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
String msg ="";
try {
msg = textMessage.getText();
} catch (JMSException e) {
e.printStackTrace();
}
System.out.println("topic消费者1,读取消息:"+msg);
}
});
System.in.read();
messageConsumer.close();
session.close();
}
}
package MQ.activeMQ;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQTextMessage;
import javax.jms.*;
import java.io.IOException;
public class TopicConsumer2 {
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("test-topic");
MessageConsumer messageConsumer = session.createConsumer(topic);
messageConsumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
String msg ="";
try {
msg = textMessage.getText();
} catch (JMSException e) {
e.printStackTrace();
}
System.out.println("topic消费者2,读取消息:"+msg);
}
});
System.in.read();
messageConsumer.close();
session.close();
}
}
发布订阅的模式 默认的请情况下:消息的内容不存在服务器,当生产者发送了一个消息,如果消费者之前没有订阅,就没了。?点对点的方式:默认的请情况下:将消息存储在服务器上,消费者随时来取,但是一旦一个消费者获取到了消息,这个消息就没有了。
请参考第二篇博客:ActiveMQ学习2:SpringBoot整合ActiveMQ
1. 修改配置文件activemq.xml
添加schedulerSupport=“true”,开启延迟处理。
2. 发送延迟消息
//生产消息
@RequestMapping("sendScheduleMsg")
public String sendScheduleMsg() throws JMSException {
Email email = new Email();
email.setFrom("A");
email.setFrom("B");
email.setTopic("hello");
email.setContent("How are you??");
ConnectionFactory connectionFactory = jmsMessagingTemplate.getConnectionFactory();
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);
ObjectMessage objectMessage = new ActiveMQObjectMessage();
objectMessage.setObject(email);
objectMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,1000*10);//延迟10秒发送,时间是毫秒
MessageProducer producer = session.createProducer(schedule_queue);
producer.send(objectMessage);
session.commit();
producer.close();
session.close();
return "Queue生产者发生消息:"+email;
}
我们都知道Topic模式的消息默认是非持久化的,也就是广播出去就没有了,那么如何实现持久化订阅呢???我们一起来看一下,这里基于ActiveMQ学习2:SpringBoot整合ActiveMQ的代码。
修改JmsConfiguration如下,添加1、2:
// topic模式的ListenerContainer
@Bean(name = "jmsListenerContainerTopic")
public JmsListenerContainerFactory> jmsListenerContainerTopic(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
//1.开启持久化订阅
bean.setSubscriptionDurable(true);
//2.设置客户端id
bean.setClientId("1111111111");
bean.setConnectionFactory(connectionFactory);
return bean;
}
activeMQ中的消息重发,指的是消息可以被broker重新分派给消费者,不一定的之前的消费者。重发消息之后,消费者可以重新消费。消息重发的情况有以下几种。
1.事务会话中,当还未进行session.commit()时,进行session.rollback(),那么所有还没commit的消息都会进行重发。
2.使用客户端手动确认的方式时,还未进行确认并且执行Session.recover(),那么所有还没acknowledge的消息都会进行重发。
3.所有未ack的消息,当进行session.closed()关闭事务,那么所有还没ack的消息broker端都会进行重发,而且是马上重发。
4.消息被消费者拉取之后,超时没有响应ack,消息会被broker重发。
重发指的是消息经过broker重新进行转发给消费者,经过测试,1和2的情况消息重发会发送给原来的消费者,3和4可以转发消息给别的消费者。累计次数超过设置的maximumRedeliveries时消息都会都会进入死信队列。
当一个消息被接收的次数超过maximumRedeliveries(默认为6次)次数时,会给broker发送一个poison _ack,这种ack类型告诉broker这个消息“有毒”,尝试多次依然失败,这时broker会将这个消息发送到DLQ,以便后续处理。activeMQ默认的死信队列是ActiveMQ.DLQ,如果没有特别指定,死信消息都会被发送到这个队列。
默认情况下持久消息过期都会被送到DLQ,非持久消息过期默认不会送到DLQ。
可以通过配置文件为指定队列创建死信队列。
修改JmsConfiguration代码
/**
* 配置Queue与Topoic并存
*/
@Configuration
public class JmsConfiguration {
// topic模式的ListenerContainer
@Bean(name = "jmsListenerContainerTopic")
public JmsListenerContainerFactory> jmsListenerContainerTopic(ConnectionFactory connectionFactory ) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
//**************消息重发**********
//1.开启事务
bean.setSessionTransacted(true);
//2.配置重发策略
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);
ActiveMQConnectionFactory activeMQConnectionFactory = (ActiveMQConnectionFactory)connectionFactory;
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
bean.setConnectionFactory( activeMQConnectionFactory);
return bean;
}
}
修改Consumer 代码
/**
* 消费者
*
*/
@Component
public class Consumer {
@JmsListener(destination=DestinationList.test_topic,containerFactory = "jmsListenerContainerTopic")
public void msgConsumerTopic1(Serializable msg, Session session ){
try {
Email email = (Email)msg;
System.out.println("Topic消费者接收到消息:"+ email.toString());
int i = 1/0;//模拟异常
System.out.println("消息处理成功!!!");
}catch (Exception e){
try {
session.rollback();//******触发消息重发
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
请参考:ActiveMQ面试题总结