直接介绍在项目中使用的配置过程(队列模式为例,点对点)
版本情况:
- springboot:1.5.9.RELEASE
- activemq:5.18
生产者
- pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!--pom.xml也要加入下面这个依赖包,否则启动报JmsMessagingTemplate注入失败 。-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>
- yml
activemq:
broker-url: tcp://localhost:61616
user: admin
password: admin
- 生产者配置类
@Configuration
@EnableJms
public class ActivemqCfg {
@Autowired
private JmsProperties jmsProperties;
private String queueName = "test-queue";
//这里声明一个queue的bean,如果要使用topic new topic即可
@Bean
public Queue queue() {
ActiveMQQueue activeMQQueue = new ActiveMQQueue(queueName);
return activeMQQueue;
}
@Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
jmsTemplate.setPubSubDomain(jmsProperties.isPubSubDomain());
JmsProperties.Template template = jmsProperties.getTemplate();
if (template.getDefaultDestination() != null) {
jmsTemplate.setDefaultDestinationName(template.getDefaultDestination());
}
if (template.getDeliveryDelay() != null) {
jmsTemplate.setDeliveryDelay(template.getDeliveryDelay().toMillis());
}
jmsTemplate.setExplicitQosEnabled(template.determineQosEnabled());
if (template.getDeliveryMode() != null) {
jmsTemplate.setDeliveryMode(template.getDeliveryMode().getValue());
}
if (template.getPriority() != null) {
jmsTemplate.setPriority(template.getPriority());
}
if (template.getTimeToLive() != null) {
jmsTemplate.setTimeToLive(template.getTimeToLive().toMillis());
}
if (template.getReceiveTimeout() != null) {
jmsTemplate.setReceiveTimeout(template.getReceiveTimeout().toMillis());
}
//持久化消息
jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
//开启事务
jmsTemplate.setSessionTransacted(true);
//客户端签收模式
jmsTemplate.setSessionAcknowledgeMode(4);
return jmsTemplate;
}
}
- 生产者发送消息
@Autowired
JmsTemplate jmsTemplate;
@Autowired
private Queue queue;
@RequestMapping("/queue/send")
public String sendQueue() throws JMSException {
jmsTemplate.convertAndSend(queue, "abc");
logger.info("发送消息成功");
return "success";
}
- 消费者配置类
@Configuration
@EnableJms
public class ActivemqCfg {
@Autowired
private JmsProperties jmsProperties;
private String queueName = "test-queue";
@Bean
public Queue queue() {
ActiveMQQueue activeMQQueue = new ActiveMQQueue(queueName);
return activeMQQueue;
}
@Bean
public RedeliveryPolicy redeliveryPolicy(){
RedeliveryPolicy redeliveryPolicy= new RedeliveryPolicy();
//是否在每次尝试重新发送失败后,增长这个等待时间
redeliveryPolicy.setUseExponentialBackOff(false);
//重发次数,默认为6次 这里设置为3次
redeliveryPolicy.setMaximumRedeliveries(3);
//重发时间间隔,默认为1秒
redeliveryPolicy.setInitialRedeliveryDelay(1000);
//第一次失败后重新发送之前等待500毫秒,第二次失败再等待500 * 2毫秒,这里的2就是value
redeliveryPolicy.setBackOffMultiplier(2);
//是否避免消息碰撞
redeliveryPolicy.setUseCollisionAvoidance(false);
//设置重发最大拖延时间-1 表示没有拖延只有UseExponentialBackOff(true)为true时生效
redeliveryPolicy.setMaximumRedeliveryDelay(-1);
return redeliveryPolicy;
}
@Bean
public ActiveMQConnectionFactory activeMQConnectionFactory (@Value("${spring.activemq.broker-url}")String url, RedeliveryPolicy redeliveryPolicy){
ActiveMQConnectionFactory activeMQConnectionFactory =
new ActiveMQConnectionFactory(
"admin",
"admin",
url);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
return activeMQConnectionFactory;
}
//定义一个消息监听器连接工厂,这里定义的是点对点模式的监听器连接工厂
@Bean(name = "jmsQueueListener")
public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory(ActiveMQConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(activeMQConnectionFactory);
//设置连接数
factory.setConcurrency("1-10");
//重连间隔时间
factory.setRecoveryInterval(1000L);
/**
* AUTO_ACKNOWLEDGE = 1 自动确认
CLIENT_ACKNOWLEDGE = 2 客户端手动确认
DUPS_OK_ACKNOWLEDGE = 3 自动批量确认
SESSION_TRANSACTED = 0 事务提交并确认
INDIVIDUAL_ACKNOWLEDGE = 4 单条消息确认 activemq 独有
*/
factory.setSessionAcknowledgeMode(2);
return factory;
}
- 消费消息
@Component
public class Consumer {
Logger logger = LoggerFactory.getLogger(getClass());
@JmsListener(destination = "test-queue", containerFactory = "jmsQueueListener")
public void receive(TextMessage textMessage, Session session) throws JMSException {
logger.info("消息为*********"+textMessage.getText());
textMessage.acknowledge();// 使用手动签收模式,需要手动的调用,如果不在catch中调用session.recover()消息只会在重启服务后重发
//session.recover();//手动触发 重发机制
}
}
Activemq的高级特性:
- 异步投递:
Activemq支持同步,异步两种发送的模式将消息发送到broker,模式的选择对发送延时有巨大的影响。使用异步发送可以显著的提高发送端的性能。
Activemq默认使用异步发送模式:除非明确指定使用同步发送的方式,或者在未使用事务的前提下发送持久化的消息,这两中情况都是同步发送的。
如果是同步的发送模式,每一次发送都是同步发送的且会阻塞producer直到broker返回一个确认,表示消息已经被安全的持久化到磁盘,确认机制提供了消息安全的保障,但同时会阻塞客户端带来了很大的延时。
//设置异步投递
ActiveMQConnectionFactory connectionFactory =
new ActiveMQConnectionFactory(URL);
connectionFactory.setUseAsyncSend(true);
异步发送丢失消息的场景是:生产者设置UserAsyncSend=true,使用producer.send(msg)持续发送消息,由于消息不阻塞,生产者会认为所有send的消息都被成功发送至mq,如果mq突然宕机,此时生产者端内存中尚未被发送到mq的消息都会丢失。所以正确的异步发送方法是需要接收回调的。
ActiveMQMessageProducer messageProducer =
(ActiveMQMessageProducer) session.createProducer(queue);
//发送消息的时候第二个参数是回调
messageProducer.send(textMessage, new AsyncCallback() {
@Override
public void onSuccess() {
System.out.println("success");
}
@Override
public void onException(JMSException exception) {
System.out.println("failed");
}
});
但是想法奇特的我有了一个奇怪的想法,看上面我们知道异步投递成功了之后会有一个回调通知,告诉投递成功,那么我想如果是持久化消息,那么消息一直在持久化中阻塞了会是什么结果。所以我把持久化机制改成了jdbc的方式,在持久化的表上锁来模拟一直持久化阻塞这个场景,,(由于配置jdbc持久化不是这个的重点,也很容易,所以这里就省略了)
-
如果是持久化的,是同步的模式,创建好生产者之后,将mysql的activemq_msgs表锁住:
lock table activemq_msgs write
那么消息会卡在send那一行的代码上,会一直阻塞,如果释放锁:
unlock tables
那么就会发送成功。和理想状况是一样的 -
如果是异步的模式,不会阻塞,我创建了一个生产者之后,锁住activemq_msgs表使其不能持久化,这时候使用异步投递发送3条消息,在一段时间之后,由于不能持久化所以触发失败回调,这时候我将mysql的锁释放掉,可以看到瞬间队列中出现了一条待消费的消息,可是刚刚已经触发了失败回调。问题就出现了,小伙伴在使用的时候要注意这个问题。
-
延迟投递和定时 投递:
1.修改activemq配置文件:
在标签中加入属性schedulerSupport="true".
修改生产者逻辑:
//参考下面的4个变量
long delay = 3 * 1000;
long period = 4 * 1000;
int repeat = 3;
//将变量设置在消息上就ok啦
TextMessage textMessage = session.createTextMessage("哈哈" + i);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,period);
textMessage.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
-
activemq的消息重试机制 :
具体哪些情况会引起消息重发?- client用了transactions且在session中调用了rollback();
- client用了transactions且在调用commit()之前关闭或者没有commit.
- client在CLIENT_ACKNOWLEDGE的传递模式下,在session中调用了recover()
消息重发的时间间隔和次数(默认);
- 间隔1s
- 次数6
有毒消息Poison ACK:
一个消息被重发超过默认的最大重发次数(默认6次)时,消费端会给MQ发送一个"poison ack" 标识这个消息有毒,告诉broker不要再发了,这个时候broker会把这个消息放到DLQ(死信队列).
配置:
//在消费者的代码中配置
redeliveryPolicy.setMaximumRedeliveries(3);
connectionFactory.setRedeliveryPolicy(redeliveryPolicy);
Connection connection = connectionFactory.createConnection();
还可以配置:
题后话,由于作者水平有限,文章中难免会有歧义的地方,欢迎大家批评指正,作者定会及时改正