我们在基于springBoot开发的时候,需要整合ActiveMq。
一:ActiveMq的下载和启动
ActiveMq包的下载地址,本文用的是windows版的5.15.3版本,下载下来是压缩包,自行解压一个到目录下,CMD进入到解压目录下的bin目录下,执行 activemq.bat start 启动。
如果能成功访问http://localhost:8161/admin(用户名和密码默认为admin),则启动成功。
二:springBoot集成activeMQ
1:点对点
我们以springBoot2.2为例
首先需要准备两个项目,Provider和Consumer项目。
第一步:引入jar包。
compile('org.springframework.boot:spring-boot-starter-activemq')
compile group: 'org.messaginghub', name: 'pooled-jms', version: '1.1.0'
在这个地方一定要切记:
不能再引入springBoot的时候,把geronimo的包去除了。因为ActiveMq需要这个包。下面的要注解掉。
config.exclude group: "org.apache.geronimo.specs"
config.exclude group: "com.google.code.findbugs"
还有一个问题,就是启动的时候出现,JmsMessagingTemplate无法注入的问题,这是因为:springBoot2.0以下使用的是:
使用springboot2.0+及以下版本时候,maven配置依赖是:
compile group: 'org.apache.activemq', name: 'activemq-pool', version: '5.15.0'
使用springboot2.1+时候,maven配置依赖是:
compile group: 'org.messaginghub', name: 'pooled-jms', version: '1.1.0'
第二步:在代码中引用mq
在Provider和Consumer的启动类里添加:
@EnableJms
1:在Provider中需要的jar包。
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-activemq')
compile group: 'org.messaginghub', name: 'pooled-jms', version: '1.1.0'
}
准备一个BeanConfig定义消息队列
代码如下:
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.jms.Queue;
/**
* Created by zhm on 2020/5/6.
*/
@Configuration
public class BeanConfig {
//定义存放消息的队列
@Bean
public Queue queue() {
return new ActiveMQQueue("ActiveMQQueue");
}
}
所以接下来在controller中准备
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.jms.Queue;
/**
* Created by zhm on 2020/5/6.
*/
@RestController
public class ProviderController {
//注入存放消息的队列,用于下列方法一
@Autowired
private Queue queue;
//注入springboot封装的工具类
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@RequestMapping("send")
public void send(String name) {
//方法一:添加消息到消息队列
jmsMessagingTemplate.convertAndSend(queue, name);
//方法二:这种方式不需要手动创建queue,系统会自行创建名为test的队列
//jmsMessagingTemplate.convertAndSend("test", name);
}
}
启动provider,向消息队列添加数据,本次添加5条数据
http://localhost:8201/send?name=Tim
http://localhost:8201/send?name=Time
http://localhost:8201/send?name=Times
http://localhost:8201/send?name=Timess
启动consumer,控制台输出如下
成功接受NameTim
成功接受NameTime
成功接受NameTimes
成功接受NameTimess
2:广播方式
以上是点对点的方式。接下来就是广播的方式。
(1):这个时候需要在applicaton.properties中添加如下,开启广播。
spring.jms.pub-sub-domain=true
(2):接着准备ActiveMqConfig
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.jms.Topic;
/**
* Created by zhm on 2020/5/6.
*/
@Configuration
public class AcitvemqConfig {
/**
* 发布/订阅
*/
@Bean
public Topic topic() {
return new ActiveMQTopic("active.topic");
}
}
(3):在Provider中准备广播者SendController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.jms.Topic;
import java.util.UUID;
/**
* Created by zhm on 2020/5/6.
*/
@RestController
public class SendController {
//注入主题
@Autowired
private Topic topic;
//注入springboot封装的工具类
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/*
* 发送 主题消息
*/
@RequestMapping("/sendTopic")
public String sendTopic() {
String message = UUID.randomUUID().toString();
// 指定消息发送的目的地及内容
jmsMessagingTemplate.convertAndSend(this.topic, message);
return "消息发送成功!message=" + message;
}
}
接下来,在相应的consumer1和consumer2中添加如下代码
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by zhm on 2020/5/6.
*/
@Component
public class TopicCustomerController {
/*
* 监听和接收 主题消息1
*/
@JmsListener(destination = "active.topic")
public void readActiveTopic1(String message) {
System.out.println("Customer1接受到:" + message);
}
}
(3):广播这provider启动,然后 http://localhost:8201/sendTopic,执行多次,
这样在http://localhost:8161/admin/topics.jsp可以看到广播信息
(4):启动consumer1和consumer2,这样控制台就收到了:
Customer1接受到:e720d5ec-c748-448a-8ebd-f8a104ec7baa
三:面试问题。
(1):ActiveMQ消息发送失败
ActiveMQ有两种通信方式,点到点形式和发布订阅模式。
a:如果是点到点模式的话,如果消息发送不成功,此消息默认会保存到ActiveMQ服务端直到有消费者将其消费,所以此消息是不会丢失的。
b:如果是发布订阅模式的通信方式,默认情况只通知一次,如果接受不到此消息就没有了,这种场景使用于对消息发送率要求不高的情况,
c:如果要求消息必须送达不可以丢失的话,需要配置持久订阅。每个订阅端定义一个id,在订阅是向ActiveMQ注册,发布消息和接受消息时需要配置发送模式为持久化,此时如果客户端接受不到消息,消息会持久化到服务端,直到客户端正常接收后为止。
(2):如何防止消息重复发送
- 解决方法:增加消息状态表。
- 通俗来说就是一个账本,用来记录消息的处理状态,每次处理消息之前,都去状态表中查询一次,如果已经有相同的消息存在,那么不处理,可以防止重复发送。
(3):丢消息怎么办
- 解决方案:用持久化消息【可以使用对数据进行持久化JDBC,AMQ(日志文件),KahaDB和LevelDB】,或者非持久化消息及时处理不要堆积,或者启动事务,启动事务后,commit()方法会负责任的等待服务器的返回,也就不会关闭连接导致消息丢失了。
(4):服务挂掉
这得从ActiveMQ的储存机制说起。在通常的情况下,非持久化消息是存储在内存中的,持久化消息是存储在文件中的,它们的最大限制在配置文件的<systemUsage>节点中配置。但是,在非持久化消息堆积到一定程度,内存告急的时候,ActiveMQ会将内存中的非持久化消息写入临时文件中,以腾出内存。虽然都保存到了文件里,但它和持久化消息的区别是,重启后持久化消息会从文件中恢复,非持久化的临时文件会直接删除