Java消息中间件入门与实践

一、Java消息中间件简介

1.1 消息中间件概述

  • 中间件:非底层操作系统软件,非业务应用软件,不是直接给最终用户使用的,不能直接给客户带来价值的软件统称为中间件。
  • 消息中间件:关注与数据的发送和接受,利用高效可靠的异步消息传递机制集成分布式系统。
  • JMS:Java消息服务(Java Message Service)即JMS,是一个Java平台中关于面向消息中间件的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
  • AMQP:AMQP(advanced message queuing protocol)是一个提供统一消息服务的应用层标准协议,基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。
  • JMS和AMQP对比
    在这里插入图片描述

1.2 消息中间件图示

在这里插入图片描述

1.3 常见消息中间件对比

1.3.1 ActiveMQ

1 概述

ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS 1.1和J2EE 1.4规范的,JMS Provider实现,尽管JMS规范出台已经很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。

2 特性
  • 多种语言和协议编写客户端。语言:Java、C、C++、C#、Ruby、Perl、Python、PHP。应用协议:OpenWire、Stomp REST、WS、Notification、XMPP、AMQP
  • 完全支持JMS 1.1和J2EE规范(持久化,XA消息,事务)
  • 虚拟主题,组合目的,镜像队列

1.3.2 RabbitMQ

1 概述

RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写。用于分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
ActiveMQ由Java消息服务(JMS)客户端组成。它能够支持多个客户端或服务器。此外,它的功能如计算机集群有助于管理通信。此外,还有两个版本的ActiveMQ; ActiveMQ 5“Classic”和ActiveMQ Artemis。
ActiveMQ 5“Classic”是一个可插拔的架构,具有JMS 1.1完整客户端实现,包括JNDI。此外,还有可用于持久性的KahaDB和JDBC选项。此外,还有一个用于分布式负载的代理网络。
ActiveMQ Artemis是一种用于事件驱动的消息传递应用程序的高性能,非阻塞体系结构。它包含JMS 1.1和2.0以及完整的客户端实现,包括JNDI。此外,还有一个灵活的集群来分配负载。此外,还有一个功能强大的协议无关寻址模型,它还提供了轻松的迁移。

2 特性
  • 支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript等
  • AMQP的完整实现(vhost虚拟主机、Exchange交换器、Binding绑定、Routing Key路由器等)
  • 事务支持/发布确认
  • 消息持久化

1.3.3 Kafka

1 概述

Kafka是一种高吞吐量的分布式发布订阅消息系统,是一个分布式的、分区的、可靠的分布式日志存储服务。它通过一种独一无二的设计提供了一个消息系统的功能。

2 特性
  • 通过O(1)的算法复杂度的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能
  • 高吞吐量:即使是非常普通的硬件Kafka也可以支持每秒钟数百万的消息
  • Partition、Consumer Group

1.3.4 综合评价

在这里插入图片描述

1.4 JMS

1.4.1 JMS规范

1 JMS相关概念
  • 提供者:实现JMS规范的消息中间件服务器
  • 客户端:发送或接收消息的应用程序
  • 生产者/发布者:创建并发送消息的客户端
  • 消费者/订阅者:接收并处理消息的客户端
  • 消息:应用程序之间传递的数据内容
  • 消息模式:在客户端之间传递消息的方式,JMS中定义了主题和队列两种模式
2 JMS消息模式
a 队列模型
  • 客户端包括生产者和消费者
  • 队列中的消息只能被一个消费者消费
  • 消费者可以随时消费队列中的消息
  • 示意图如下:
    在这里插入图片描述
b 主体模型
  • 客户端包括发布者和订阅者
  • 主题中的消息被所有订阅者消费
  • 消费者不能消费订阅之前就发送到主题中的消息,即只能先订阅才能消费
  • 示意图如下:
    在这里插入图片描述
3 JMS编码接口
1 常用接口
  • ConnectionFactory 用于创建连接到消息中间件的链接工厂
  • Connection 代表了应用程序和消息服务器之间的通信链路
  • Destination 指消息发布和接收的地点,包括队列或主题
  • Session 表示一个单线程的上下文,用于发送和接收消息
  • MessageConsumer 由会话创建,用于接收发送到目标的消息
  • MessageProducer 由会话创建,用于发送消息到目标
  • Message 是在消费者和生产者之间传送的对象,消息头,一组消息属性,一个消息体
2 JMS编程接口之间的关系

在这里插入图片描述

二、ActiveMQ的安装与启动

2.1 在Windows平台安装ActiveMQ

  • 下载安装包地址:http://activemq.apache.org/components/classic/download/
  • 直接启动,以管理员身份运行启动你电脑对应版本的activemq.bat,即可成功启动,比如,我的是64位的,即选择如下图中的win64文件夹下的activemq.bat,或者安装成Windows服务,以服务方式启动,记住使用以管理员身份运行。
    在这里插入图片描述
  • 使用服务启动,右击ActiveMQ这条服务,可以设置为手动启动,或者自动启动,即在开机时就启动。
    在这里插入图片描述
  • 启动成功后,使用浏览器访问127.0.0.1:8161就可以访问ActiveMQ的管理页面,点击Manage ActiveMQ broker,出现弹框输入用户名密码,都默认为admin,然后就可以进入管理主页了!
    在这里插入图片描述
    备注:
    activeMQ默认配置下启动会启动8161和61616两个端口,其中8161是mq自带的管理后台的端口,61616是mq服务默认端口 。
    8161是后台管理系统,61616是给java用的tcp端口。

2.2 在Linux平台安装ActiveMQ

  • 下载安装包地址:http://activemq.apache.org/components/classic/download/
  • 使用wget下载: wget https://www-us.apache.org/dist//activemq/5.15.9/apache-activemq-5.15.9-bin.tar.gz
  • 解压安装包 tar -zxvf apache-activemq-5.15.2-bin.tar.gz
  • 启动,进入解压后产生的目录,使用 ./activemq start 启动,可以访问linux服务器的ip地址加端口8161,能进入管理页面即成功安装并启动,使用./activemq stop 关闭。
  • 备注:activemq运行需要用到JDK,
    可以使用如下命令进行安装JDK
查看支持的JDK版本:
yum -y list java*
ps:带-devel的安装的是jdk,而不带-devel的,实际上安装的是jre
执行如下命令安装JDK
yum install -y java-1.8.0-openjdk-devel.x86_64

三、JMS的代码实现

3.1 队列模型代码实现

  • 1、新建一个maven工程
    在这里插入图片描述
  • 2、添加pom依赖
    从这里获取依赖关系:https://mvnrepository.com/artifact/org.apache.activemq/activemq-all/5.15.9
        <!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-all</artifactId>
            <version>5.15.9</version>
        </dependency>
  • 3、创建生产者AppProducter
package com.nick;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @auther: wangteng
 * 向消息中间件发送消息
 */
public class AppProducter {
    //http://localhost:8161
    //activemq的地址
    private static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
    //指定队列的名字
    private static final String QUEUE_NAME="activemq_queue";

    /**
     * main方法
     * @param args
     */
    public static void main(String[] args) throws JMSException {
        //1、创建ConnectionFactory
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2、创建Connection
        Connection connection = connectionFactory.createConnection();
        //3、启动连接
        connection.start();
        //4、创建会话 第一个参数的意思是是否在事务里面进行处理
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        //5、创建一个目标
        Destination destination = session.createQueue(QUEUE_NAME);
        //6、创建一个生产者
        MessageProducer producter = session.createProducer(destination);
        //7、开始循环的向目标地址发送消息
        for(int i=0;i< 100; i++){
            //8、创建消息
            TextMessage textMessage = session.createTextMessage("你好,JMS!这是第"+i+"个消息。");
            producter.send(textMessage);
            System.out.println("发型消息"+textMessage+"成功");
        }
        //9、关闭连接
        connection.close();
    }
}

运行此程序,可以看到控制台的日志,
在这里插入图片描述
登录activeMQ后台页面可以看到刚才生产者发送的消息,如下,
在这里插入图片描述

  • 4、创建生产者AppConsumer
package com.nick;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @auther: wangteng
 * 从中间件获取消息进行消费
 * 8161是后台管理系统,61616是给java用的tcp端口。
 */
public class AppConsumer {
    //activemq的地址
    private static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
    //指定队列的名字
    private static final String QUEUE_NAME="activemq_queue";

    /**
     * main方法
     * @param args
     */
    public static void main(String[] args) throws JMSException {
        //1、创建ConnectionFactory
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2、创建Connection
        Connection connection = connectionFactory.createConnection();
        //3、启动连接
        connection.start();
        //4、创建会话 第一个参数的意思是是否在事务里面进行处理
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        //5、创建一个目标
        Destination destination = session.createQueue(QUEUE_NAME);
        //6、创建一个消费者
        MessageConsumer consumer = session.createConsumer(destination);
        //7、创建一个监听器
        consumer.setMessageListener(new MessageListener() {
            public void onMessage(Message message) {
                TextMessage textMessage = (TextMessage)message;
                try {
                    System.out.println("消费者接收到消息="+ textMessage.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
        //8、这里切记不要关闭连接否则会导致消息无法持续接收
        //connection.close();
    }
}

执行消费者程序代码,控制台会打印消费的消息日志,
在这里插入图片描述
登录activeMQ后台管理系统看下队列中的消息是否被消费掉了,
在这里插入图片描述

  • 5、一个生产者多个消费的运行情况
    开启main程序类并行的开关,选中allow parallel run保存即可。
    在这里插入图片描述
  • a、运行两个AppConsumer.java,开启两个消费者
  • b、再次运行下生产者,生成消息
  • c、观察下消费者消费的情况
    消费者1:
    在这里插入图片描述
    消费者2:
    在这里插入图片描述
    如上消费者消费的情况是不是和上面讲解的JMS消息模式中的队列模式一样。
    在这里插入图片描述

3.2 主题模型代码实现

  • 1、在3.1的工程下面创建包名com.nick.topicmodel,
  • 2、创建生产者AppProducter
package com.nick.topicmodel;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @auther: wangteng
 * 向消息中间件发送消息
 * 8161是后台管理系统,61616是给java用的tcp端口。
 */
public class AppProducter {
    //http://localhost:8161
    //activemq的地址
    private static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
    //指定队列的名字
    private static final String TOPIC_NAME="activemq_topic";

    /**
     * main方法
     * @param args
     */
    public static void main(String[] args) throws JMSException {
        //1、创建ConnectionFactory
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2、创建Connection
        Connection connection = connectionFactory.createConnection();
        //3、启动连接
        connection.start();
        //4、创建会话 第一个参数的意思是是否在事务里面进行处理
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        //5、创建一个目标  和队列模式的代码唯一不一样的地方,其它都是一样的。
        Destination destination = session.createTopic(TOPIC_NAME);
        //6、创建一个生产者
        MessageProducer producter = session.createProducer(destination);
        //7、开始循环的向目标地址发送消息
        for(int i=0;i< 100; i++){
            //8、创建消息
            TextMessage textMessage = session.createTextMessage("你好,JMS!这是第"+i+"个消息。");
            producter.send(textMessage);
            System.out.println("发型消息"+textMessage+"成功");
        }
        //9、关闭连接
        connection.close();
    }
}
  • 3、创建消费者AppConsumer
package com.nick.topicmodel;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * @auther: wangteng
 * 从中间件获取消息进行消费
 * 8161是后台管理系统,61616是给java用的tcp端口。
 */
public class AppConsumer {
    //activemq的地址
    private static final String ACTIVEMQ_URL="tcp://127.0.0.1:61616";
    //指定队列的名字
    private static final String TOPIC_NAME="activemq_topic";

    /**
     * main方法
     * @param args
     */
    public static void main(String[] args) throws JMSException {
        //1、创建ConnectionFactory
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2、创建Connection
        Connection connection = connectionFactory.createConnection();
        //3、启动连接
        connection.start();
        //4、创建会话 第一个参数的意思是是否在事务里面进行处理
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        //5、创建一个目标 和队列模式的代码唯一不一样的地方,其它都是一样的。
        Destination destination = session.createTopic(TOPIC_NAME);
        //6、创建一个消费者
        MessageConsumer consumer = session.createConsumer(destination);
        //7、创建一个监听器
        consumer.setMessageListener(new MessageListener() {
            public void onMessage(Message message) {
                TextMessage textMessage = (TextMessage)message;
                try {
                    System.out.println("消费者接收到消息="+ textMessage.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
        //8、这里切记不要关闭连接否则会导致消息无法持续接收
        //connection.close();
    }
}
  • 4、启动生成者AppProducter,登录activeMQ后台管理页面,查看下topic菜单,
    在这里插入图片描述
  • 5、下面启动两个消费者,
    在这里插入图片描述
    发现并未有消费的记录,这个情况我们前面也讲解过对于主题模式的场景,消费者不能消费订阅之前就发送到主题中的消息,即只能先订阅才能消费。大家可以翻看下前面的信息。
    重新启动下生产者,生产一批新的消息,
    消费者1:
    在这里插入图片描述
    消费者2:
    在这里插入图片描述
    可以看到刚刚生产的100个消息,两个消费者会各自全部消费掉,和上面的队列模式是不一样的情况。
    在这里插入图片描述

四、 spring JMS理论

4.1 spring提供的接口

  • ConnectionFactory用于管理连接的连接工厂
    • spring提供的连接池
    • JMSTemplate 每次都会重新创建连接,会话和productor
    • spring中提供了SingleConnectionFactory和CachingConnectionFactory
      • SingleConnectionFactory 对于建立JMS服务器连接的请求只会返回一个同一个Connection,也就是说 在整个应用中只会使用一个连接进行操作
      • CachingConnectionFactory 继承了SingleConnectionFactory,所以它拥有了SingleConnectionFactory它所有的功能,同时还新增了缓存功能,缓存producer和consumer
  • JmsTemplate用户发送和接受消息的模板类
    • 是spring提供的,只需要向spring容器内注册这个类就可以使用jmsTemplate方便操作jms
    • JmsTemplate 是线程安全的,可以在整个应用范围使用,但是并不代表整个应用中只能使用一个,我们可以创建多个。
  • MessageListerner 消息监听器
    • 实现一个onMessage方法,该方法只接受一个message参数

4.2 spring jms代码实现

4.2.1 队列模式代码实现

  • 1、首先,创建一个maven工程
    在这里插入图片描述
  • 2、添加pom依赖关系
<properties>
        <spring.version>4.2.5.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-core</artifactId>
            <version>5.7.0</version>
            <exclusions>
                <!-- 因为我们自己已经引用了这个包,并且这个包中也附带了这个包,所以需要排除掉 -->
                <exclusion>
                    <artifactId>spring-context</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
  • 3、配置spring配置文件
    在resources目录下面新增spring配置文件,
    在这里插入图片描述
  • common.xml,每个配置项都有注释请仔细观察,
    另外,这个文件中的context:annotation-config这个节点的意思是激活spring的注解能力。
    这个文件中配置的是生产者和消费者公共使用的工具类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config></context:annotation-config>
    <!-- 由ActiveMQ提供的ConnectionFactory-->
    <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>
    <!-- 由spring jms提供的连接池-->
    <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
        <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
    </bean>
    <!-- 配置队列模式的地址,队列目的地,点对点的-->
    <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg value="queue_model_name"></constructor-arg>
    </bean>
    <!-- 配置主题模式的地址-->
    <bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg value="topic_model_name"/>
    </bean>
</beans>
  • 创建生产者的spring配置文件producer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <import resource="common.xml"/>

    <!-- 创建jmsTemplate,用于发送消息-->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory" />
    </bean>
    <bean class="com.nick.producter.ProducerServiceImpl"></bean>
</beans>
  • 创建消费者的spring配置文件consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 引入公共配置项-->
    <import resource="common.xml"/>

    <!-- 配置消息监听器-->
    <bean id="consumerMessageListener" class="com.nick.consumer.ConsumerMessageListener"/>

    <!-- 配置消息的容器,该容器需要依赖下面三个工具类-->
    <bean id="jmsListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <!--  队列模式的目的地-->
        <property name="destination" ref="queueDestination"/>
        
        <!--  主题模式的目的地
        <property name="destination" ref="topicDestination"/>
        -->

        <property name="messageListener" ref="consumerMessageListener"/>
    </bean>

</beans>
  • 4、实现生产者功能代码
  • 接口ProducerService.java
package com.nick.producter;

/**
 * @author wangteng
 * @version 1.0.0
 * @ClassName ProducerService.java
 * @Description 生产者接口类
 * @createTime 2019年08月22日 23:19:00
 */
public interface ProducerService {
    /**
    *@Description: 发送消息
    *@Author: wangteng
    *@date: 2019/8/22
    */
    public void sendMessage(String message);

}
  • 接口实现类ProducerServiceImpl.java
package com.nick.producter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import javax.annotation.Resource;
import javax.jms.*;

/**
 * @author wangteng
 * @version 1.0.0
 * @ClassName ProducerServiceImpl.java
 * @Description 生产者接口类
 * @createTime 2019年08月22日 23:19:00
 */
public class ProducerServiceImpl implements  ProducerService {
    @Autowired
    JmsTemplate jmsTemplate;

    //@Resource(name = "queueDestination")
    @Resource(name = "topicDestination")
    Destination destination;
    /**
    *@Description: 发送消息
    *@Author: wangteng
    *@date: 2019/8/22
    */
    public void sendMessage(final String message){
        jmsTemplate.send(destination, new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                TextMessage textMessage= session.createTextMessage(message);
                return textMessage;
            }
        });
        System.out.println("发送消息="+ message + " 成功!");
    }
}

  • 生产者主程序类AppProducer.java
package com.nick.producter;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author wangteng
 * @version 1.0.0
 * @ClassName AppProducer.java
 * @Description TODO
 * @createTime 2019年08月23日 09:51:00
 */
public class AppProducer {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("producer.xml");
        ProducerService service = context.getBean(ProducerService.class);
        for (int i = 0; i < 100; i++){
            service.sendMessage("spring jms消息"+ i);
        }
        context.close();
    }
}

  • 5、实现消费者功能代码
  • 创建消费者监听器
    ConsumerMessageListener.java
package com.nick.consumer;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

/**
 * @author wangteng
 * @version 1.0.0
 * @ClassName ConsumerMessageListener.java
 * @Description TODO
 * @createTime 2019年08月23日 10:28:00
 */
public class ConsumerMessageListener implements MessageListener {
    /**
    *@Description: 消息监听
    *@Author: wangteng
    *@date: 2019/8/23
    */
    public void onMessage(Message message){
        TextMessage textMessage = (TextMessage)message;
        try {
            System.out.println("消费者接收到消息"+textMessage.getText());
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}
  • 创建消费者主程序类AppConsumer.java
package com.nick.consumer;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author wangteng
 * @version 1.0.0
 * @ClassName AppConsumer.java
 * @Description TODO
 * @createTime 2019年08月23日 10:36:00
 */
public class AppConsumer {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
    }
}

  • 6、首先,启动两个消费者主程序类,然后,启动生产者主程序类,看下队列模式下的消费情况
  • 生产者:
    在这里插入图片描述
  • 消费者1
    在这里插入图片描述
  • 消费者2
    在这里插入图片描述

4.2.2 主题模式代码实现

主题模式的代码和队列模式相对,只需要修改两个地方即可,
1、ProducerServiceImpl.java修改发送的目的地

    @Resource(name = "queueDestination")
    //@Resource(name = "topicDestination")
    Destination destination;

2、consumer.xml修改消息容器依赖的目的地

    <!-- 配置消息的容器,该容器需要依赖下面三个工具类-->
    <bean id="jmsListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <!--  队列模式的目的地  -->
        <property name="destination" ref="queueDestination"/>

        <!--  主题模式的目的地
        <property name="destination" ref="topicDestination"/>
        -->
        <property name="messageListener" ref="consumerMessageListener"/>
    </bean>

接下就可以运行看下效果,这里就不贴图,小伙伴们自行测试下吧。

五、 activeMQ集群

5.1 为什么要实现activeMQ集群

  • 高可用:排除单点故障引起的服务中断。
  • 负载均衡:提示效率为更多的客户提供服务。

5.2 集群方式

  • 客户端集群:让多个消费者消费同一个队列。
  • ActiveMQ broker clusters:多个broker之间同步消息。
  • Master slave:主备模式,高可用。

5.3 activeMQ失效转移机制failove

该策略用于控制消费者的访问,这是我们在编写代码的时候要使用的连接方式。一个消费者连接到多个broker集群的中的一个broker,当该broker出问题时,消费者自动连接到其他一个正常的broker。消费者使用 failover 协议来连接broker,通常叫做 失效转移(也叫故障转移,断线重连机制,FailOver)策略,语法如下:
failover:(uri1,uri2,…,uriN)?transportOptions

1.uri:消息服务器的地址 
2.transportOptions参数说明: 
        randomize:默认为 true ,表示在URI列表中选择URL连接时是否采用随机策略。 
        initialReconnectDelay:默认为10,单位为毫秒,表示一次尝试重连之间等待的时间。 
        maxReconnectDelay:默认 30000,单位毫秒,最长重连的时间间隔。

例如,

<!-- 
    1. client使用failover协议来与有效的master交互  
    2. master地址在前,slavew 在后,randomize 为 false让 Client优先与master通信  
    3. 如果 master 失效,failover协议将会尝试与slave建立链接,并依此重试
-->
failover:(tcp://localhost:61616,tcp://localhost:61617)?randomize=false

5.4 Broker Clusters 部署

在这里插入图片描述

  • Broker-Cluster的部署方式就可以解决负载均衡的问题。Broker-Cluster部署方式中,各个broker通过网络互相连接,并共享queue,保证消息同步。
  • 各个broker进行消息同步使用的是NetworkConnection (网络连接器),主要用于配置各个broker之间的网络通讯方式,用于服务器传递信息。 分为静态连接器和动态连接器。
    静态连接器
<networkConnectors>
    <networkConnector uri="static:(tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)"/> 
</networkConnectors>

动态连接器(服务器比较多的情况下使用)

<!-- 网络连接器 -->
<networkConnectors>
    <networkConnector uri="multicast://default"/> 
</networkConnectors>
<!-- 传输连接器 -->
<transprotConnectors>
    <transprotConnector uri="tcp://localhost:0" discoveryUri="multicast://default" />
</transprotConnectors>

静态连接器过于局限,动态连接器可随意扩展服务器连接。

5.5 Broker Clusters 部署Master Slave 部署(主从)

通过部署多个broker实例,选举产生一个master和多个slave,master宕机后由slave接管服务来达到高可用性。Master-Slave的方式虽然能解决多服务热备的高可用问题,但无法解决负载均衡和分布式的问题。Broker Cluster的部署方式刚好可以解决负载均衡的问题。一般两者结合使用。

1、Share storage master slave(共享存储)

在这里插入图片描述
这里主要介绍2种配置方案:包括:Shared File System Master slave 和 JDBC Store Master Slave 两种模式
此模式中Master和Slave的数据是共享的(相当于共享同一个数据库),当master失效后,slave会自动接管服务,无需手动进行数据的copy与同步,因为master存储数据之后,这些数据在任何时候对slave都是可见的。
master与slave之间,通过共享文件的“排他锁”或者分布式排他锁(ZooKeeper)来决定Broker的状态与角色,获取锁权限的Broker作为master,其它的Broker则作为slave。如果master失效,它必将失去锁权限,那么其它的slave将通过锁竞争来选举新master,没有获取锁权限的Broker作为slave,并等待锁的释放(间歇性尝试获取锁)。

  • Shared File System Master Slave模式(只适合单台主机部署,不适合多台主机部署)
  • JDBC Store Master Slave模式(适合多台主机部署)

2、Replicated LevelDB Store(基于复制的LevelDB Store,使用ZooKeeper协调多个Broker)

ZooKeeper的稳定性需要至少3个节点,
在这里插入图片描述

5.6 Master Slave和Broker Cluster结合使用(常用方式)

在这里插入图片描述
这个集群是综合了Broker Cluster和master/slave两种基本集群方式,其中master/slave(B和C)是基于共享存储实现的。A和B组成消息同步,A和C组成消息同步是为实现均衡负载,B和C组成master/slave是为了实现高可用。

如果A宕机,集群退化成标准master/slave集群,只是了失去负载均衡能力。
如果B宕机,C会继续提供服务,集群退化成Broker Cluster集群,失去高可用能力。
如果C宕机也会失去高可用能力(同B)。
ABC无论哪一台宕机,集群都不会崩溃,但是需要迅速恢复。

5.7 activeMQ集群实践

  • 1、配置方案:
    在这里插入图片描述
    我这里只有一台服务器,我采用修改端口的方式模拟三个activeMQ服务器。
  • 2、环境信息:
    在这里插入图片描述
  • 3、部署3个activeMQ服务:
    在这里插入图片描述
    按照上面的环境信息进行配置:
  • a、打开activemq.xml文件,修改端口&添加networkCoonnector(网络连接器)
    在这里插入图片描述
  • b、打开jetty.xml文件,配置端口
    在这里插入图片描述
  • c、activemq-a、activemq-b、activemq-c参见上面的两个截图及其上面的环境信息的表格进行配置。
  • d、启动三个activeMQ服务
    可以使用netstat -anlp|grep 61616查看服务端口监听情况,注意NodeB和NodeC节点互为主备,不会同时提供监听服务的。
  • e、使用之前写的demo代码连接下这个集群的activeMQ环境,
    修改下消费者和生产者程序代码中的URL地址,使用失效转移机制,
  • AppConsumer消费者:
//三个节点都可以当消费者
private static final String ACTIVEMQ_URL="failover:(tcp://39.96.194.34:61616,tcp://39.96.194.34:61617,tcp://39.96.194.34:61618)?randomize=false";
  • AppProducter生产者:
//NodeA节点只能当消费者,不能作为生产者,这里只配置两个节点的地址
private static final String ACTIVEMQ_URL="failover:(tcp://39.96.194.34:61617,tcp://39.96.194.34:61618)?randomize=false";

先启动消费者,再启动生产者,测试完成。
在这里插入图片描述

六、 其它消息中间件

5.1 RabbitMQ

在这里插入图片描述
在这里插入图片描述

5.2 kafka

在这里插入图片描述
在这里插入图片描述

七、企业系统中的最佳实践

  • 下面看下一个业务场景:
    在这里插入图片描述
  • 面临的问题:
    在这里插入图片描述
  • 问题1:使用JMS级联的解决方案
    JMS存在两种消息模式:发布订阅、消息队列。结合两种模式同时应用。
    发布者将消息发布到JMS主题
    通过两个中转器分别订阅主题,接收主题中的消息
    中转器将消息发送到JMS队列中
    集群中的系统负责消费
    在这里插入图片描述
  • 问题2:使用JMS中XA系列接口引入分布式事务保证强一致性。
    使用JMS中XA系列接口引入分布式事务保证强一致性。
    • 引入分布式事务
    • 要求业务操作必须支持XA协议
      如果业务操作支持XA协议,并且不对性能有强要求,这将是一个简单的解决方案。
      但在互联网业务中,对强一致性要求不高,对性能要求更高。

一致性
从效果上可以分为两种:
- 强一致性
在同一时刻,要么都成功,要么都失败。
- 弱一致性
不对时间做保证,但随着时间的推移,不同节点间的数据最终会保持一致,也就是最终一致性。

  • 使用消息表的本地事务解决方案
    图中存在一个业务数据和消息记录,另外还存在一个消息中间件。
    1、现在把业务数据和消息记录通过本地事务组合在一起,然后发起业务请求到本地事务。
    2、本地事务完成后,发起消息到消息中间件。
    3、消息中间件接收成功,更新消息状态。
    4、假设第二步发送消息到消息中间件失败,则消息保存在待发送消息中,这时需要提供一个补偿机制。
    4.1、补偿机制轮询消息记录。
    4.2、将待发送的消息重新发送到消息中间件。
    4.3、消息成功发送后,消息中间件更新消息状态。
    在这里插入图片描述
    问题
    虽然避开了分布式事务,但还是要求业务必须全部基于数据库内的事务系统。所以需要更通用的方式解决一致性问题。
    使用内存日志的解决方案
    图中存在一个业务数据和内存日志,另外还存在一个消息中间件。
    1、现在发起业务请求,进行业务处理。
    2、业务处理成功,将待发送日志存储到内存日志中。
    3、然后将消息发送给消息中间件。
    4、如果发送成功,移除内存日志中的该条消息日志。
    5、假设第三步发送失败,则需要补偿机制。
    5.1、补偿机制轮询内存日志。
    5.2、将未成功发送的消息重新发送到消息中间件。
    5.3、如果发送成功,将移除日志。
    该内存日志可以使用ehcache,支持将内存日志持久化到本地文件,同时还支持集群复制,保证当前业务子系统宕机,还保证消息不丢失。
  • 问题3:消息处理端的幂等性问题
    什么是幂等性
    多次执行所产生的影响与一次执行所产生的影响一样。
    如支付成功后,支付服务商会多次通知业务系统;业务系统需要处理这些重复的消息,但又不重复处理订单。
    消息处理系统中保证幂等性,会增加系统复杂度。我们可以统一处理幂等性后,再将消息发送给消息处理系统。
    本地事务处理幂等性
    通过本地事务将消息和业务组合在一起。
    1、消费者从消息中间件获取消息。
    2、消费者查询消费日志是否处理过
    3、如果没有处理过,消费者使用本地事务处理业务
    4、处理成功后,更改消息状态,发送给消息中间件。
    在这里插入图片描述
    内存日志处理幂等性
    1、消费者从消息中间件接收消息
    2、查询本地内存日志
    3、如果当前消息没有被处理,则进行处理
    4、处理成功后,更新本地内存日志
    5、向消息中间件发送确认消息
    在这里插入图片描述
  • 问题4:基于消息机制的事件总线
    什么是事件驱动架构
    事件驱动架构(Event Driven Architecture, EDA)定义了一个设计和实现一个应用系统的方法学,在这个系统里事件可传输于松耦合的组件和服务之间。
    特点:有事我叫你,没事别烦我
    发布者是事件的发起者,消费者是事件的影响人。
    事件驱动架构模型

首先有三个业务系统,然后使用事件总线来简化业务系统之间的事件发布。

  • 业务处理系统通过事件总线提供的方法进行事件注册。
  • 业务发起系统通过事件总线提供的方法发起事件。
  • 事件总线封装了消息的发送和接收,以及内存日志的处理。但此时它还不具备消息中间件的功能,需要定义一个抽象的消息提供者。
  • 根据不同的消息中间件提供具体的消息提供者实现。
    在这里插入图片描述
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值