ActiveMQ学习总结
ActiveMQ下载地址:ActiveMQ
一、Windows环境下安装ActiveMQ并启动
1.直接解压压缩包
2.进入目录直接启动
3.访问http://127.0.0.1:8161/并登录(账号密码在conf文件夹中的users.properties中)
二、Linux环境下安装ActiveMq并启动
1.将apache-activemq-5.16.0-bin.tar.gz传到Linux下
2.解压
- tar -zxvf apache-activemq-5.16.0-bin.tar.gz
3.复制到/usr/local/activemq下(可不复制)
- cp -a apache-activemq-5.16.0 /usr/local/activemq
4.进入bin目录并启动
- cd /usr/local/cativemq/bin
- ./activemq start
5.查看是否启动成功
- ps -ef|grep activemq
root 13410 1 21 16:53 pts/0 00:00:06 /usr/local/software/jdk1.8.0_171/bin/java -Xms64M -Xmx1G -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=/usr/local/activem//conf/login.config -Dcom.sun.management.jmxremote -Djava.awt.headless=true -Djava.io.tmpdir=/usr/local/activemq//tmp -Dactivemq.classpath=/usr/local/activemq//conf:/usr/local/activemq//../lib/: -Dactivemq.home=/usr/local/activemq/ -Dactivemq.base=/usr/local/activemq/ -Dactivemq.conf=/usr/local/activemq//conf -Dactivemq.data=/usr/local/activemq//data -jar /usr/local/activemq//bin/activemq.jar start
root 13451 13280 0 16:54 pts/0 00:00:00 grep --color=auto activemq
6.将端口开放8161和61616
7.(重点)如果安全组和防火墙都放行了8161和61616还是无法访问就进入conf文件中的jetty.xml将127.0.0.1修改为0.0.0.0并重启activemq
三、ActiveMQ的使用
1.创建Maven项目
2.配置POM
<properties>
<activemq.version>5.15.5</activemq.version>
<slf4j.version>1.7.25</slf4j.version>
<lombok.version>1.16.20</lombok.version>
</properties>
<dependencies>
<!-- ActiveMQ配置-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>${activemq.version}</version>
</dependency>
<!-- slf4j通用配置-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- lombok配置-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
3.点对点模式(Queue模式队列)
3.1 生产者发送消息(默认是持久)
- 创建ConnectionFactory对象,需要指定服务端IP及端口号
- 使用ConnectionFactory对象创建一个Connection对象
- 开启连接,调用Connection对象的start方法
- 使用Connection对象创建一个Session对象(是否开启事务,接收者的签收状态-自动签收)
- 使用Session对象创建一个Destination对象(topic/queue),此处创建一个Queue对象
- 使用Session对象创建一个Producer对象
- 创建一个Messageu对象,创建一个TextMessage对象
- 使用Producer对象发送消息
- 关闭资源
public class TestQueueProducer {
private static String BROKER_URL = "tcp://**.**.**.**:61616";
private static String QUEUE_NAME="QUEUE-HELLO";
public static void main(String[] args) throws JMSException {
ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageProducer producer = session.createProducer(queue);
// TextMessage message = new ActiveMQTextMessage();
TextMessage message = session.createTextMessage();
message.setText("hello");
producer.send(message);
producer.close();
session.close();
connection.close();
System.out.println("消息发送成功!");
}
}
异步生产:
public class TestQueueProducerSync {
private static String BROKER_URL = "tcp://**.**.**.**:61616";
private static String QUEUE_NAME="QUEUE-HELLO";
public static void main(String[] args) throws JMSException {
ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageProducer producer = session.createProducer(queue);
for (int i = 1;i<=10;i++){
TextMessage message = session.createTextMessage();
message.setText("hello"+i);
producer.send(message);
}
producer.close();
session.close();
connection.close();
System.out.println("消息发送成功!");
}
3.2 消费者消费消息
- 创建ConnectionFactory对象,需要指定服务端IP及端口号
- 使用ConnectionFactory对象创建一个Connection对象
- 开启连接,调用Connection对象的start方法
- 使用Connection对象创建一个Session对象(是否开启事务,接收者的签收状态-自动签收)
- 使用Session对象创建一个Destination对象(topic/queue),此处创建一个Queue对象
- 使用Session对象创建一个Consumer对象
- 监听消息
- 关闭资源
public class TestQueueConsumer {
private static String BROKER_URL = "tcp://**.**.**.**:61616";
private static String QUEUE_NAME="QUEUE-HELLO";
public static void main(String[] args) throws JMSException {
ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(queue);
Message message = consumer.receive(2000);
if (message instanceof TextMessage){
TextMessage text= (TextMessage) message;
System.out.println("接收到了消息,消息内容为:"+text.getText());
}
session.close();
connection.close();
System.out.println("消息接收成功!");
}
}
异步消费:
public class TestQueueConsumerSync {
private static String BROKER_URL = "tcp://**.**.**.**:61616";
private static String QUEUE_NAME="QUEUE-HELLO";
public static void main(String[] args) throws JMSException, IOException {
ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(queue);
//异步使用setMessageListener
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
if (message instanceof TextMessage){
try {
System.out.println("接收到了消息:"+((TextMessage) message).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
System.in.read();
session.close();
connection.close();
System.out.println("消息接收成功!");
}
}
3.3 三大消费情况
- 先生产,只启动1号消费者。1号消费者能消费到消息吗--------->可以
- 先生产,先启动1号消费者再启动2号消费者。2号消费者还能消费消息吗?-------->不可以,全部被1号消费完了
- 先启动2个消费者,再生产10条消息。消费情况如何?-------->一人一半,轮询消费
4.发布/订阅模式(Topic模式队列)
先启动消费者再启动生产者,不然发送的就是废消息(先关注订阅号才能收到消息)
1.生产消息
public class TestTopicProducer {
public static void main(String[] args) throws JMSException {
String brokerURL = "tcp://**.**.**.**:61616";
String topicName = "topicName";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//topic
Topic topic = session.createTopic(topicName);
MessageProducer producer = session.createProducer(topic);
for (int i=1;i<=10;i++){
TextMessage textMessage = session.createTextMessage("topic-message" + i);
producer.send(textMessage);
}
producer.close();
session.close();
connection.close();
System.out.println("主题发送完成!");
}
}
2.消费消息
活动非持久化(默认)
public class TestTopicConsumer {
public static void main(String[] args) throws JMSException, IOException {
String brokerURL = "tcp://**.**.**.**:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
String topicName = "topicName";
Topic topic = session.createTopic(topicName);
System.out.println("2号消费者");
MessageConsumer consumer = session.createConsumer(topic);
consumer.setMessageListener(new MessageListener() {
@SneakyThrows
public void onMessage(Message message) {
if (message instanceof TextMessage){
System.out.println("接收到消息:"+((TextMessage) message).getText());
}
}
});
System.in.read();
session.close();
connection.close();
System.out.println("主题发送完成!");
}
}
活动持久化(设置ClientID)
public class TestTopicConsumerDurable {
public static void main(String[] args) throws JMSException, IOException {
String brokerURL = "tcp://**.**.**.**:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL);
Connection connection = factory.createConnection();
connection.setClientID("陈少磊"); //设置订阅者的ID
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
String topicName = "topicName";
Topic topic = session.createTopic(topicName);
// 创建一个持久的主题订阅者
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark");
System.out.println("陈少磊消费者");
topicSubscriber.setMessageListener(new MessageListener() {
@SneakyThrows
public void onMessage(Message message) {
if (message instanceof TextMessage){
System.out.println("接收到消息:"+((TextMessage) message).getText());
}
}
});
System.in.read();
session.close();
connection.close();
System.out.println("主题发送完成!");
}
}
5.点对点与发布/订阅模式的比较
Topic模式队列 | Queue模式队列 | |
---|---|---|
工作模式 | “订阅-发布”模式,如果当前没有订阅者,消息将会被丢弃。如果有多个订阅者,那么这些订阅者都会接收到消息 | “负载均衡”模式,如果当前没有消费者,消息也不会被丢弃;如果有多个消费者,那么一条消息也只会发送给启动一个消费者,并且要求消费者ack信息 |
有无状态 | 无状态 | Queue数据默认回在MQ服务器上以文件形式保存,比如ActiveMQ一般保存在$AMQ_HOME\data\kr-store\data下,也可以配置成DB存储。 |
传递性能 | 如果没有订阅者,消息会被丢弃 | 消息不会被丢弃 |
处理效率 | 由于消息要按照订阅者的数量进行复制,所以处理性能回随着订阅者的增加而明显降低,并且还要结合不同消息协议自身的性能差异 | 由于一条消息只发送给一个消费者,所以就算消费者再多,性能也不会明显降低,当然不同消息协议的具体性能也有差异的。 |
6.JMS Messager
6.1 消息头
JMS消息头包含了许多字段,它们是消息发送后由JMS提供者或消息发送者产生,用来表示消息、设置优先权和失效时间等等,并且为消息确定路由。
- JMSDesination:消息发送的目的地。只要指Queue和Topic;
- JMSDeliveyMode:消息的持久模式和非持久模式;
- JMSExpiration:消息的过期设置,默认为永不过期;
- JMSPriority:消息优先级,0-9十个级别:0-4为普通级别、5-9为加急消息;
- JMSMessageID:唯一识别每个消息的表示,由MQ产生。
- JMSCorrelationID:通常用来关联多个Message。
- JMSRedelivered:如果这个值为true,表示消息是被重新发送了。
- JMSReplyTo:有时消息生产者希望消费者回复一个消息,JMSReplyTo为一个Destination,表示需要回复的目的地。当然消费者可以不理会它。
- JMSTimestamp:当调用send()方法的时候,JMSTimestamp会被自动设置为当前时间戳。
long timestamp = message.getJMSTimestamp();
- JMSType:表示消息体的结构,和JMS提供者有关。
属性名 | 描述 | 类型 | 默认值 | 设置者 |
---|---|---|---|---|
JMSDesination | 发送消息目的地 | Destination | 生产者set进去的 | send |
JMSDeliveyMode | 是否要持久化 | int | DeliveryMode.PERSISTENT(持久) | send |
JMSExpiration | 用户定义 | long | 0 | send |
JMSPriority | 0-9 | int | 4 | send |
JMSMessageID | 消息唯一标示符 | String | unique | send |
JMSCorrelationID | 用户定义 | String | null | client |
JMSRedelivered | 是否重新传递 | boolean | false | provider |
JMSReplyTo | 用户定义 | Destination | null | client |
JMSTimestamp | 毫秒 | long | 消息发送时间 | send |
JMSType | 用户定义 | String | “” | client |
6.2 消息体
封装具体的消息数据,5种消息体格式,发送和接受的消息必须一致对应
-
TextMessage: 一个字符串对象
-
StreamMessage: Java原始的数据流
-
MapMessage: 键值对
-
BytesMessage:一个字节的数据流
-
ObjectMessage: 一个序列化的Java对象,需放行所有包的安全
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL);
// 放行所有包的安全检查
factory.setTrustAllPackages(true);
6.3 消息属性
由消息发送者产生,用来添加消息头以外的附加信息。
- 应用程序设置和添加的属性。
message.setStringProperty("username",username);
- JMS定义的属性。
- JMS供应商特定的属性
7. ActiveMQ签收
生产者偏向事务,消费者偏向签收。
7.1 非事务模式下签收
-
生产者签收设置为自动,消费者签收设置为自动---->正常
-
生产者签收设置为自动,消费者签收设置为手动,未ACK->重复消费
-
生产者签收设置为自动,消费者签收设置为手动,已ACK->正常
-
生产者签收设置为手动,消费者签收设置为自动->正常
-
生产者签收设置为手动,消费者签收设置为手动,未ACK---->重复消费
-
生产者签收设置为手动,消费者签收设置为手动,已ACK ----->正常
总结:消费者需自动就正常,消费者手动需ACK就正常
7.2 有事务模式下签收(都开启了事务)
如果生产者设置有事务但不提交,消息发送不出去;必须在send之后调用session.commit();
producer.send(message);
session.commit();
-
生产者签收设置为自动,消费者签收设置为自动,已提交->正常
-
生产者签收设置为自动,消费者签收设置为手动,已提交,未ACK->正常
-
生产者签收设置为自动,消费者签收设置为自动,未提交->重复消费
-
生产者签收设置为自动,消费者签收设置为手动,未提交,已ACK->重复消费
-
生产者签收设置为手动,消费者签收设置为自动,已提交-> 正常
-
生产者签收设置为手动,消费者签收设置为自动,未提交 -> 重复消费
-
生产者签收设置为手动,消费者签收设置为自动末,未提交,未ACK -> 重复消费
-
生产者签收设置为手动,消费者签收设置为自动,未提交,已ACK -> 重复消费
-
生产者签收设置为手动,消费者签收设置为手动,已提交,未ACK->正常
-
生产者签收设置为手动,消费者签收设置为手动,已提交,已ACK -> 正常
总结:消费者需提交就正常(即提交就是签收)
四、内嵌MQ的使用(Broker)
1.启动Broker
public class EmbedBroker {
public static void main(String[] args) {
BrokerService service = new BrokerService();
service.setUseJmx(true);
try {
service.addConnector("tcp://127.0.0.1:61616");
service.start();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("内嵌MQ启动成功!");
}
}
2.启动生产者
public class QueueProducer {
private static String BROKER_URL = "tcp://127.0.0.1:61616";
private static String QUEUE_NAME="QUEUE-HELLO";
public static void main(String[] args) throws JMSException {
ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageProducer producer = session.createProducer(queue);
TextMessage message = session.createTextMessage();
message.setText("hello");
message.setStringProperty("flag","??/");
producer.send(message);
producer.close();
session.close();
connection.close();
System.out.println("消息发送成功!");
}
}
3.启动消费者
public class QueueConsumer {
private static String BROKER_URL = "tcp://127.0.0.1:61616";
private static String QUEUE_NAME="QUEUE-HELLO";
public static void main(String[] args) throws JMSException {
ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(queue);
Message message = consumer.receive(2000);
if (message instanceof TextMessage){
TextMessage text= (TextMessage) message;
message.acknowledge();
System.out.println("接收到了消息,消息内容为:"+text.getText()+" "+message.getStringProperty("flag"));
}
session.close();
connection.close();
}
}