跟我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)定义了一个设计和实现一个应用系统的方法学,在这个系统里事件可传输于松耦合的组件和服务之间。
特点:有事我叫你,没事别烦我
发布者是事件的发起者,消费者是事件的影响人。
事件驱动架构模型
首先有三个业务系统,然后使用事件总线来简化业务系统之间的事件发布。
- 业务处理系统通过事件总线提供的方法进行事件注册。
- 业务发起系统通过事件总线提供的方法发起事件。
- 事件总线封装了消息的发送和接收,以及内存日志的处理。但此时它还不具备消息中间件的功能,需要定义一个抽象的消息提供者。
- 根据不同的消息中间件提供具体的消息提供者实现。