MQ 安装

一、下载安装:

操作系统:centos7

  1. wget http://archive.apache.org/dist/activemq/5.15.10/apache-activemq-5.15.10-bin.tar.gz
  2. tar -zxvf apache-activemq-5.15.10-bin.tar.gz
  3. ./activemq start > ../mylog.log  启动mq,并将日志记录进指定文件
  4. 防火墙开放 8161(默认访问端口,提供页面控制台服务)和 61616(默认进程端口,提供JMS服务)
  5. http://ip:8161/admin 进行访问,初始账号密码:admin/admin

二、使用

1、pom.xml文件引入activemq的jar包
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-all</artifactId>
    <version>5.15.10</version>
</dependency>
<dependency>
    <groupId>org.apache.xbean</groupId>
    <artifactId>xbean-spring</artifactId>
    <version>3.16</version>
</dependency>

三、原理

底层是JMS,java消息服务,J2EE中的一个重要规范,定义了两个应用程序之间异步通信的API接口,使我们的程序可以到达异步、解耦、削峰的效果。

  • JMS简介

消息模型分为2种:

  1. 点对点模式Point-to-Point(P2P),队列queue
  2. 发布/订阅模式Publish/Subscribe(Pub/Sub),topic主题

点对点模式Point-to-Point(P2P),队列queue

一条消息仅能对应一个消费者,多个消费者一个生产者的情况,每个消费者轮询获取消息;

消息被消费后,队列中不会再有该条消息;

消费者和生产者的启动顺序可以随意;

如果没有消费者,消息也不会被丢弃;

 

示例

创建一个点对点的生产者

        String brokerUrl = "tcp://localhost:61616";
        String queueName = "第一个消息队列";
        // 1、创建连接工厂
        ActiveMQConnectionFactory connectionFactory = new                 ActiveMQConnectionFactory(brokerUrl);
        // 2、创建连接
        Connection connection = connectionFactory.createConnection();
        connection.start();
        // 3、创建会话session  第一个参数:是否开启事务;第二个参数:应答模式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 4、创建目的地
        Queue queue = session.createQueue(queueName);
        // 5、创建消息生产者
        MessageProducer producer = session.createProducer(queue);
        // 6、创建3条消息
        for (int i = 1; i <= 3; i++) {
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText("第"+i+"条消息");
            // 7、生产者发送消息
            producer.send(textMessage);
        }
        // 8、关闭资源
        producer.close();
        session.close();
        connection.close();

点对对消费者

public static void main(String[] args) throws JMSException, IOException {
        String brokerUrl = "tcp://localhost:61616";
        String queueName = "第一个消息队列";
        // 1、创建连接工厂
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
        // 2、创建连接
        Connection connection = connectionFactory.createConnection();
        connection.start();
        // 3、创建会话session  第一个参数:是否开启事务;第二个参数:应答模式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 4、创建目的地
        Queue queue = session.createQueue(queueName);
        // 5、创建消息消费者
        MessageConsumer consumer = session.createConsumer(queue);
        // 设置异步非阻塞消息监听器获取消息
        consumer.setMessageListener((message)->{
            TextMessage message1 = (TextMessage) message;
            try {
                System.out.println("我是消费者1:"+message1.getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        });
        // 阻塞,防止未获取消息就结束了
        System.in.read();
        // 8、关闭资源
        consumer.close();
        session.close();
        connection.close();
    }

发布/订阅模式Publish/Subscribe(Pub/Sub),topic主题

一条消息可以对应多个消费者;

消费者只能接收订阅以后的消息,订阅前的消息获取不到,所以要先启动消费者再启动生产者;

如果没有消费者,消息会被丢弃;

示例

创建 发布/订阅 模式的生产者

public static void main(String[] args) throws Exception{
        String brokerUrl = "tcp://localhost:61616";
        String topicName = "第一个topic消息队列";
        // 1、创建连接工厂
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
        // 2、创建连接
        Connection connection = connectionFactory.createConnection();
        connection.start();
        // 3、创建会话session  第一个参数:是否开启事务;第二个参数:应答模式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 4、创建目的地
        Topic topic = session.createTopic(topicName);
        // 5、创建消息生产者
        MessageProducer producer = session.createProducer(topic);
        // 6、创建3条消息
        for (int i = 1; i <= 5; i++) {
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText("第"+i+"条消息");
            // 7、生产者发送消息
            producer.send(textMessage);
        }
        // 8、关闭资源
        producer.close();
        session.close();
        connection.close();
    }

创建 发布/订阅 模式的消费者

public static void main(String[] args) throws Exception{
        String brokerUrl = "tcp://localhost:61616";
        String topicName = "第一个topic消息队列";
        // 1、创建连接工厂
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
        // 2、创建连接
        Connection connection = connectionFactory.createConnection();
        connection.start();
        // 3、创建会话session  第一个参数:是否开启事务;第二个参数:应答模式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 4、创建目的地
        Topic topic = session.createTopic(topicName);
        // 5、创建消息消费者
        MessageConsumer consumer = session.createConsumer(topic);
        // 设置异步非阻塞消息监听器获取消息
        consumer.setMessageListener((message)->{
            TextMessage message1 = (TextMessage) message;
            try {
                System.out.println("我是topic消费者1:"+message1.getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        });
        // 阻塞,放着未获取消息就结束了
        System.in.read();
        // 8、关闭资源
        consumer.close();
        session.close();
        connection.close();
    }

消息

有两种存储方式:

持久化:服务器宕机,消息依然存在 (队列的默认方式)

持久化订阅者在订阅以后,如果离线了,下次再上线依然可以接收到未消费的消息

producer.setDeliveryMode(DeliveryMode.PERSISTENT);//持久化
connection.start();// 启动连接需要放在这里才生效

非持久化:服务器宕机,消息丢失

producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);//非持久化
connection.start();// 启动连接需要放在这里才生效

Session

签收方式:

// 签收与服务端没关系,主要是针对客户端

1、未开启事务模式
// 客户端自动签收
connection.createSession(false, Session.AUTO_ACKNOWLEDGE); 

// 客户端手动签收,搭配textMessage.acknowledge()使用
connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
textMessage.acknowledge(); 

2、开启事务模式
// 客户端自动签收
connection.createSession(true, Session.CLIENT_ACKNOWLEDGE);
session.commit();

// 客户端手动签收
// 在事务环境下,签收与事务有关,acknowledge不起作用,事务提交就自动签收;事务不提交,客户端签收不起作用
connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
textMessage.acknowledge(); 
session.commit();

 

java 代码内嵌一个Broker

pom.xml文件引入

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.9.5</version>
</dependency>
    public static void main(String[] args) throws Exception {
        //创建内嵌broker
        BrokerService brokerService = new BrokerService();
        brokerService.setUseJmx(true);
        brokerService.addConnector("tcp://localhost:61617");
        brokerService.start();
    }

四、springboot 引入activeMq

复习 @Autowired 和 @Resource 的区别

@Autowired//默认按type注入,spring注解
@Qualifier("cusInfoService")//一般作为@Autowired()的修饰用,按照name注入
@Resource(name="cusInfoService")//默认按name注入,可以通过name和type属性进行选择性注入,javaee注解

pom.xml

<!--    引入jms    -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

application.yml

spring:
  activemq:
    broker-url: tcp://localhost:61616
    user: admin
    password: admin
  jms:
    pub-sub-domain: false # 默认false queue;true topic

destination: boot-activemq-queue # 自己定义队列名称

启动类

@SpringBootApplication
@EnableScheduling // 开启定时任务
public class Main8007 {
    public static void main(String[] args) {
        SpringApplication.run(Main8007.class,args);
    }
}

目的地-queue

@Component
@EnableJms // 开启jms
public class DestinationBean {
    @Value("destination") // 获取application.yml文件自定义的名称
    private String destination;

    @Bean
    public Queue queue(){
        return new ActiveMQQueue(destination);
    }
}

生产者

@Component
public class ProducerBean {
    @Autowired
    private JmsMessagingTemplate template;

    @Autowired
    private Queue queue;

    public void produceMessage(){
        template.convertAndSend(queue,"22222");
    }

    @Scheduled(fixedDelay = 3000) // 每隔3秒生成一条消息
    public void produceMessageScheduled(){
        template.convertAndSend(queue,"33333");
    }
}

消费者

@Component
public class ConsumerBean {

    @JmsListener(destination = "${destination}")
    public void receiveMessage(TextMessage message) throws JMSException {
        System.out.println("消费者消费:"+message.getText());
    }
}

五、传输协议

默认的是openwire,即tcp://ip:port

NIO协议,比tcp协议性能更好,生产使用此种协议

修改activemq.xml配置文件,增加nio协议

        <transportConnectors>
            <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
            <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="nio" uri="nio://0.0.0.0:61618?trace=true"/>
        </transportConnectors>
        <!-- 自动适配各种协议 -->
        <transportConnectors>
            <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
            <transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61618?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600&amp;org.apache.activemq.transport.nio.SelectorManager.corePoolSize=20&amp;org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50"/>
        </transportConnectors>

六、MQ的高可用性

事务、持久、签收

  1. 持久化到mysql数据库

lib目录下引入jdbc驱动,修改activemq.xml文件

        <persistenceAdapter>
            <!-- <kahaDB directory="${activemq.data}/kahadb"/> -->
			<!-- createTablesOnStartup = true 每次启动的时候会重新创建数据库,建议初始化后改为false -->
			<jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#activemq-mysql" createTablesOnStartup="true"/>
        </persistenceAdapter>

<!-- 下边的代码需要加在 broker 和 import 标签中间 -->
<!-- 若要使用druid数据库连接池,需要手动引入jar包 -->
    <bean id="activemq-mysql" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/activemq?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC&amp;relaxAutoCommit=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="poolPreparedStatements" value="true"/>
    </bean>

手动创建一个activemq数据库

CREATE DATABASE activemq DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

重启activemq服务后,会自动在activemq库中创建3张表,以后的mq数据都在这三张表里存放着

ACTIVEMQ_MSGS 存放持久化消息

  1. ID:自增的数据库主键
  2. CONTAINER:消息的Destination
  3. MSGID_PROD:消息发送者客户端的主键
  4. MSG_SEQ:是发送消息的顺序,MSGID_PROD+MSG_SEQ可以组成JMS的MessageID
  5. EXPIRATION:消息的过期时间,存储的是从1970-01-01到现在的毫秒数
  6. MSG:消息本体的Java序列化对象的二进制数据
  7. PRIORITY:优先级,从0-9,数值越大优先级越高

ACTIVEMQ_ACKS 用于存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存

  1. CONTAINER:消息的Destination
  2. SUB_DEST:如果是使用Static集群,这个字段会有集群其他系统的信息
  3. CLIENT_ID:每个订阅者都必须有一个唯一的客户端ID用以区分
  4. SUB_NAME:订阅者名称
  5. SELECTOR:选择器,可以选择只消费满足条件的消息。条件可以用自定义属性实现,可支持多属性AND和OR操作
  6. LAST_ACKED_ID:记录消费过的消息的ID。

ACTIVEMQ_LOCK 

创建持久化订阅者,存放在 ACTIVEMQ_ACKS 中

    public static void main(String[] args) throws Exception{
        String brokerUrl = "nio://localhost:61618";
        String topicName = "topic消息队列";
        // 1、创建连接工厂
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
        // 2、创建连接
        Connection connection = connectionFactory.createConnection();
        // 3、设置持久订阅消费者ID
        connection.setClientID("持久化消费者ID1");
        // 4、创建会话session  第一个参数:是否开启事务;第二个参数:应答模式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 4、创建目的地
        Topic topic = session.createTopic(topicName);
        // 5、创建持久订阅者
        TopicSubscriber c1 = session.createDurableSubscriber(topic, "持久化topic消费者1");
        // 6、启动连接
        connection.start();
        // 7、设置异步非阻塞消息监听器获取消息
        c1.setMessageListener((message)->{
            TextMessage message1 = (TextMessage) message;
            try {
                System.out.println("我是topic消费者:"+message1.getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        });
        System.in.read();
        // 8、关闭资源
        c1.close();
        session.close();
        connection.close();
    }

创建持久化发布者,发布的消息存放在ACTIVEMQ_MSGS中

    public static void main(String[] args) throws Exception{
        String brokerUrl = "nio://localhost:61618";
        String topicName = "topic消息队列";
        // 1、创建连接工厂
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
        // 2、创建连接
        Connection connection = connectionFactory.createConnection();
        // 3、创建会话session  第一个参数:是否开启事务;第二个参数:应答模式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 4、创建目的地
        Topic topic = session.createTopic(topicName);
        // 5、创建消息生产者
        MessageProducer producer = session.createProducer(topic);
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);//持久化
        connection.start();
        // 6、创建3条消息
        for (int i = 1; i <= 5; i++) {
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText("第"+i+"条消息");
            // 7、生产者发送消息
            producer.send(textMessage);
            textMessage.acknowledge();
        }
        // 8、关闭资源
//        session.commit();
        producer.close();
        session.close();
        connection.close();
    }

七、在activemq和mysql之间增加一层高速缓存 ActiveMq journal

不断的读写数据库很消耗服务器性能,增加一层高速缓存,可以让快速消费掉的消息不写入数据库,而且消息先存入缓存中,比直接读数据库要快。

消息不是立马就同步到数据库中,在journal中存放一段时间之后才会同步到数据库中,变相的做到了读写分离操作

修改activemq.xml文件,重启服务即可

        <!-- <persistenceAdapter>
            <kahaDB directory="${activemq.data}/kahadb"/>
        			createTablesOnStartup = true 每次启动的时候会重新创建数据库,建议用完改为false
        			<jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#activemq-mysql" createTablesOnStartup="true"/>
        </persistenceAdapter> -->
		<persistenceFactory>
			<journalPersistenceAdapterFactory journalLogFiles="4" journalLogFileSize="32768" userJournal="true" useQuickJournal="true" dataDirectory="${activemq.base}/data" dataSource="#activemq-mysql" createTablesOnStartup="false"/>
		</persistenceFactory>

八、高可用

使用zookeeper + replicated-leveldb-store 模式的主从集群实现高可用性

activemq集群,客户端只能访问master,其他slaver只做数据拷贝;

当master宕机后,会自动选举出一个新的master,原master恢复以后,进入slaver集群

zookeeper 半数选举机制选取master;机器数量超过一半以上的存活,该集群才可用

九、面试题

mq默认使用异步方式发送数据,但是有两种情况会使用同步方式发送消息:

  1. 显式使用同步方式
  2. 未使用事务的情况下发送持久化消息

 

异步发送消息会出现消息丢失的情况:假如MQ宕机了,生产者内存中尚未发送到MQ的数据会全部丢失

解决方案:接收回调结果

    public static void main(String[] args) throws JMSException {
        String brokerUrl = "tcp://localhost:61618";
        String queueName = "第一个消息队列";
        // 1、创建连接工厂
        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(brokerUrl);
        connectionFactory.setAlwaysSyncSend(true);// 设置异步发送
        // 2、创建连接
        Connection connection = connectionFactory.createConnection();
        connection.start();
        // 3、创建会话session  第一个参数:是否开启事务;第二个参数:应答模式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 4、创建目的地
        Queue queue = session.createQueue(queueName);
        // 5、创建消息生产者
        ActiveMQMessageProducer producer = (ActiveMQMessageProducer)session.createProducer(queue);
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);// 发送持久化消息
        // 6、创建3条消息
        for (int i = 1; i <= 5; i++) {
            TextMessage textMessage = session.createTextMessage();
            textMessage.setText("第"+i+"条消息");
            textMessage.setJMSMessageID(UUID.randomUUID().toString());
            // 消息ID用于记录某条消息的发送状态
            String messageId = textMessage.getJMSMessageID();
            // 7、生产者发送消息,回调函数用于接收消息发送的结果
            producer.send(textMessage, new AsyncCallback() {
                @Override
                public void onSuccess() {
                    // 消息发送成功回调函数
                    System.out.println("消息发送成功,消息ID:"+messageId);
                }

                @Override
                public void onException(JMSException e) {
                    // 消息发送失败回调函数
                    System.out.println("消息发送失败,消息ID:"+messageId);
                }
            });
        }
        // 8、关闭资源
        producer.close();
        session.close();
        connection.close();
    }

 

消息重发机制

默认重发机制:每隔一秒重发一次,一共重发6次,重发超过6次以后进入死信队列

引发消息重发机制的2种情况:

  1. 客户端-消费者:开启了事务,但是未成功调用session.commit()
  2. 客户端-消费者:应答模式为CLIENT_ACKNOWLEDGE,在session中调用了recover

 

如何解决消息重复消费

重复消费产生原因:消息重发
 
解决方案
一条数据重复出现多次的情况下,给消息增加一个唯一ID,这样就能保证数据库里只有一条数据,也就保证了系统的幂等性。

比如
某个数据要写库,首先根据主键查一下,如果这个数据已经有了,那就别插入了,执行update即可;
如果用的是redis,那就没问题了,因为每次都是set操作,天然的幂等性

幂等性是什么?
幂等性就是一个数据,或者一个请求,给你执行多次,得保证对应的数据不会改变,并且不能出错,这就是幂等性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值