ActiveMQ(一)

官网

ActiveMQ 的官网 : http://activemq.apache.org

扩展

ActiveMQ
扩展出:

  • API 接受发送
  • MQ 的高可用
  • MQ 的集群容错配置
  • MQ 的持久化
  • 延时发送
  • 签收机制
  • Spring/SpringBoot 整合

MQ : 消息中间件/消息队列

为什么要使用 MQ ?
解决了耦合调用、异步模型、抵御洪峰流量,保护了主业务,消峰。

在linux 的opt 目录下上传 mq 的压缩包,(使用vmware-tools 上传的)
并且将压缩包放到 /myactivemq 下

直接进入myactivemq 的 文件下的activemq 下的 bin 目录,使用 ./activemq start 命令启动

检查activemq 是否启动的三种方法: 也是三种查看后台进程的方法

ps -ef|grep activemq|grep -v grep      // grep -v  grep 可以不让显示grep 本来的信息

netstat -anp|grep 61616    // activemq 的默认后台端口是61616

lsof -i:61616

docker ps -a

让启动的日志信息不在控制台打印,而放到专门的文件中:
./activemq start > /myactivemq/myrunmq.log

JMS : Java 消息中间件的服务接口规范,activemq 之上是 mq , 而 mq 之上是JMS 定义的消息规范 。 activemq 是mq 技术的一种理论实现(与之相类似的实现还有 Kafka RabbitMQ RockitMQ ),而 JMS 是更上一级的规范。
ActiveMQ(一)_第1张图片

DEMO入门

demo1–Queue

package com.db.privategoodsplatform.test;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

/**
 * @author xuqinglei
 * @date 2023/03/14 10:50
 **/
public class QueueProduce {
    //  linux 上部署的activemq 的 IP 地址 + activemq 的端口号
    public static final String ACTIVEMQ_URL = "tcp://8.130.105.216:61616";
    public static final String QUEUE_NAME = "test-queue";

    public static void main(String[] args) throws  Exception{

        // 1 按照给定的url创建连接工程,这个构造器采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 2 通过连接工厂连接 connection  和 启动
        javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
        //  启动
        connection.start();
        // 3 创建回话  session
        // 两个参数,第一个事务, 第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 4 创建目的地 (两种 : 队列/主题   这里用队列)
        Queue queue = session.createQueue(QUEUE_NAME);
        // 5 创建消息的生产者
        MessageProducer messageProducer = session.createProducer(queue);
        // 6 通过messageProducer 生产 3 条 消息发送到消息队列中
        for (int i = 1; i < 7 ; i++) {
            // 7  创建字消息
            TextMessage textMessage = session.createTextMessage("msg--" + i);
            // 8  通过messageProducer发布消息
            messageProducer.send(textMessage);
        }
        // 9 关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("  **** 消息发送到MQ完成 ****");
    }
}
package com.db.privategoodsplatform.test;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.io.IOException;

/**
 * @author xuqinglei
 * @date 2023/03/14 10:56
 **/
public class QueueConsumer {

    //  linux 上部署的activemq 的 IP 地址 + activemq 的端口号
    public static final String ACTIVEMQ_URL = "tcp://8.130.105.216:61616";
    public static final String QUEUE_NAME = "test-queue";

    public static void main(String[] args) throws JMSException, IOException {

        // 1 按照给定的url创建连接工程,这个构造器采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);

        // 2 通过连接工厂连接 connection  和 启动
        Connection connection = activeMQConnectionFactory.createConnection();

        //  启动
        connection.start();
        // 3 创建回话  session
        // 两个参数,第一个事务, 第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // 4 创建目的地 (两种 : 队列/主题   这里用队列)
        Queue queue = session.createQueue(QUEUE_NAME);

        MessageConsumer consumer = session.createConsumer(queue);

//        while(true){
//            // 这里是 TextMessage 是因为消息发送者是 TextMessage , 接受处理的
//            // 也应该是这个类型的消息
//            TextMessage message = (TextMessage)consumer.receive(1);
//            if (null != message){
//                System.out.println("****消费者的消息:"+message.getText());
//            }else {
//                break;
//            }
//        }

        consumer.setMessageListener(message -> {
            if (null != message  && message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                try {
                    System.out.println("****消费者的消息:" + textMessage.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });

        System.in.read();
        consumer.close();
        session.close();
        connection.close();

    }

}

DEMO-Topic

package com.db.privategoodsplatform.test;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @author xuqinglei
 * @date 2023/03/22 14:56
 **/
public class TopicProduce {

    //  linux 上部署的activemq 的 IP 地址 + activemq 的端口号
    public static final String ACTIVEMQ_URL = "tcp://8.130.105.216:61616";
    public static final String TOPIC_NAME = "test-topic-22";

    public static void main(String[] args) throws JMSException {
        // 1 按照给定的url创建连接工程,这个构造器采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 2 通过连接工厂连接 connection  和 启动
        javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
        //  启动
        connection.start();
        // 3 创建回话  session
        // 两个参数,第一个事务, 第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // 4 创建目的地 (两种 : 队列/主题   这里用主题 )
        Topic topic = session.createTopic(TOPIC_NAME);

        // 5 创建消息的生产者
        MessageProducer producer = session.createProducer(topic);

        // 6 通过messageProducer 生产 3 条 消息发送到消息队列中
        for (int i = 1; i < 8 ; i++) {
            // 7  创建字消息
            TextMessage textMessage = session.createTextMessage("msg--" + i);
            // 8  通过messageProducer发布消息
            producer.send(textMessage);
        }
        // 9 关闭资源
        producer.close();
        session.close();
        connection.close();

        System.out.println("  **** 消息发送到MQ完成 ****");

    }

}
package com.db.privategoodsplatform.test;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.io.IOException;

/**
 * @author xuqinglei
 * @date 2023/03/22 15:08
 **/
public class TopicConsumer {

    //  linux 上部署的activemq 的 IP 地址 + activemq 的端口号
    public static final String ACTIVEMQ_URL = "tcp://8.130.105.216:61616";
    public static final String TOPIC_NAME = "test-topic-22";

    public static void main(String[] args) throws JMSException, IOException {
        // 1 按照给定的url创建连接工程,这个构造器采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 2 通过连接工厂连接 connection  和 启动
        javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
        //  启动
        connection.start();
        // 3 创建回话  session
        // 两个参数,第一个事务, 第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // 4 创建目的地 (两种 : 队列/主题   这里用主题 )
        Topic topic = session.createTopic(TOPIC_NAME);

        MessageConsumer consumer = session.createConsumer(topic);


        consumer.setMessageListener(message -> {
            if (null != message  && message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                try {
                    System.out.println("****消费者的消息:" + textMessage.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });

        System.in.read();
        consumer.close();
        session.close();
        connection.close();
    }
}

这里的一点经验: activemq 好像自带负载均衡,当先启动两个队列(Queue)的消费者时,在启动生产者发出消息,此时的消息平均的被两个消费者消费。 并且消费者不会消费已经被消费的消息(即为已经出队的消息)
但是当有多个主题(Topic)订阅者时,发布者发布的消息,每个订阅者都会接收所有的消息。topic 更像是被广播的消息,但是缺点是不能接受已经发送过的消息。
ActiveMQ(一)_第2张图片

  • 先要有订阅者,生产者才有意义。

JMS

1.JAVAEE 是一套使用Java 进行企业级开发的13 个核心规范工业标准 , 包括:
JDBC 数据库连接
JNDI Java的命名和目录接口
EJB Enterprise java bean
RMI 远程方法调用 一般使用TCP/IP 协议
Java IDL 接口定义语言
JSP
Servlet
XML
JMS Java 消息服务
JTA
JTS
JavaMail
JAF
ActiveMQ(一)_第3张图片

2. 如何保证消息的可靠性

    JMS 可靠性:Persistent   持久性  、 事务 、 Acknowledge  签收

2.1 持久化

// 在队列为目的地的时候持久化消息
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

// 队列为目的地的非持久化消息
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
 持久化的消息,服务器宕机后消息依旧存在,只是没有入队,当服务器再次启动,消息任就会被消费。
但是非持久化的消息,服务器宕机后消息永远丢失。 而当你没有注明是否是持久化还是非持久化时,
默认是持久化的消息。
对于目的地为主题(topic)来说,默认就是非持久化的,
让主题的订阅支持化的意义在于:对于订阅了公众号的人来说,
当用户手机关机,在开机后任就可以接受到关注公众号之前发送的消息。
代码实现:持久化topic 的消费者
代码实现:持久化topic 的消费者
           ……    // 前面代码相同,不复制了      
            ……    // 前面代码相同,不复制了      
        Topic topic = session.createTopic(TOPIC_NAME);
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark...");

         // 5 发布订阅
        connection.start();

        Message message = topicSubscriber.receive();// 一直等
         while (null != message){
             TextMessage textMessage = (TextMessage)message;
             System.out.println(" 收到的持久化 topic :"+textMessage.getText());
             message = topicSubscriber.receive(3000L);    // 等1秒后meesage 为空,跳出循环,控制台关闭
         }
   ……
持久化生产者
          ……  
             MessageProducer messageProducer = session.createProducer(topic);
        // 6 通过messageProducer 生产 3 条 消息发送到消息队列中

        // 设置持久化topic 在启动
        messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); 
        connection.start();
        for (int i = 1; i < 4 ; i++) {
            // 7  创建字消息
            TextMessage textMessage = session.createTextMessage("topic_name--" + i);
            // 8  通过messageProducer发布消息
            messageProducer.send(textMessage);

            MapMessage mapMessage = session.createMapMessage();
            //    mapMessage.setString("k1","v1");
            //     messageProducer.send(mapMessage);
        }
        // 9 关闭资源
      …… 

ActiveMQ(一)_第4张图片

2.2 事务

 createSession的第一个参数为true 为开启事务,开启事务之后必须在将消息提交,才可以在队列中看到消息
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

提交:

session.commit(); 

  事务开启的意义在于,如果对于多条必须同批次传输的消息,可以使用事务,
  如果一条传输失败,可以将事务回滚,再次传输,保证数据的完整性。
  对于消息消费者来说,开启事务的话,可以避免消息被多次消费,
  以及后台和服务器数据的不一致性。举个栗子:
如果消息消费的  createSession  设置为 ture  ,但是没有 commit ,
此时就会造成非常严重的后果,那就是在后台看来消息已经被消费,
但是对于服务器来说并没有接收到消息被消费,此时就有可能被多次消费。

2.3 Acknowledge 签收 (俗称ack)

    非事务    :
Session.AUTO_ACKNOWLEDGE      自动签收,默认

Session.CLIENT_ACKNOWLEDGE     手动签收
手动签收需要acknowledge   
textMessage.acknowledge();

而对于开启事务时,设置手动签收和自动签收没有多大的意义,都默认自动签收,也就是说事务的优先级更高一些。

Session session = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);
//Session session = connection.createSession(true,Session.CLIENT_ACKNOWLEDGE);   //  也是自动签收   

        ……

session.commit();  

但是开启事务没有commit 任就会重复消费

小知识:  broker 
broker 就是实现了用代码形式启动 ActiveMQ 将 MQ 内嵌到 Java 代码中,可以随时启动,节省资源,提高了可靠性。
就是将 MQ 服务器作为了 Java 对象 

使用多个配置文件启动 activemq 

cp activemq.xml  activemq02.xml 

// 以active02 启动mq 服务器
./activemq start xbean:file:/myactivemq/apache-activemq-5.15.9/conf/activemq02.xml 


 把小型 activemq 服务器嵌入到 java 代码: 不在使用linux 的服务器
  需要的包:

  com.fasterxml.jackson.core
  jackson-databind
  2.9.5

启动代码

public class Embebroker {
    public static void main(String[] args) throws Exception {
        // broker 服务
        BrokerService brokerService = new BrokerService();
        // 把小型 activemq 服务器嵌入到 java 代码
        brokerService.setUseJmx(true);
        // 原本的是 192.……  是linux 上的服务器,而这里是本地windows 的小型mq 服务器
        brokerService.addConnector("tcp://localhost:61616");
        brokerService.start();
    }
}

你可能感兴趣的:(java-activemq,activemq,java)