前言
在说ActiveMQ之前,我们需要先了解一些概念,什么是消息,什么是消息队列?如果不考虑其他,在现实生活中两个人之间传递的信息可以理解为消息,同理在计算机中也可以理解为消息是两台计算机之间传递的数据单位,消息可以非常简单,例如只包含文本字符串; 也可以更复杂,可能包含嵌入对象。 消息被发送到队列中。
“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消 息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。
消息队列的主要特点是异步处理,主要目的是减少请求响应时间和解耦。所以主要的使 用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同 时由于使用了消息队列,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系, 也不需要受对方的影响,即解耦和。如: 跨系统的异步通信,所有需要异步交互的地方都可以使用消息队列。就像我们除了打电 话(同步)以外,还需要发短信,发电子邮件(异步)的通讯方式。 多个应用之间的耦合,由于消息是平台无关和语言无关的,而且语义上也不再是函数调 用,因此更适合作为多个应用之间的松耦合的接口。基于消息队列的耦合,不需要发送方和 接收方同时在线。 在企业应用集成(EAI)中,文件传输,共享数据库,
消息队列,远程过程调用都可以 作为集成的方法。 应用内的同步变异步,比如订单处理,就可以由前端应用将订单信息放到队列,后端应 用从队列里依次获得消息处理,高峰时的大量订单可以积压在队列里慢慢处理掉。由于同步 通常意味着阻塞,而大量线程的阻塞会降低计算机的性能。 消息驱动的架构(EDA),系统分解为消息队列,和消息制造者和消息消费者,一个处 理流程可以根据需要拆成多个阶段(Stage) ,阶段之间用队列连接起来,前一个阶段处理的 结果放入队列,后一个阶段从队列中获取消息继续处理。 应用需要更灵活的耦合方式,如发布订阅,比如可以指定路由规则。 跨局域网,甚至跨城市的通讯,比如北京机房与广州机房的应用程序的通信。
消息中间件应用场景
异步通信
有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。
想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
缓冲
在任何重要的系统中,都会有需要不同的处理时间的元素。
消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。
解耦
降低工程间的强依赖程度,针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。
通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,当应用发生变化时,
可以独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
冗余
有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
扩展性
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容。
可恢复性
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,
加入队列中的消息仍然可以在系统恢复后被处理。
顺序保证
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
过载保护
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;
如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
数据流处理
分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。
常用消息队列(ActiveMQ、RabbitMQ、RocketMQ、Kafka)比较
特性MQ | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
生产者消费者模式 | 支持 | 支持 | 支持 | 支持 |
发布订阅模式 | 支持 | 支持 | 支持 | 支持 |
请求回应模式 | 支持 | 支持 | 不支持 | 不支持 |
Api完备性 | 高 | 高 | 高 | 高 |
多语言支持 | 支持 | 支持 | java | 支持 |
单机吞吐量 | 万级 | 万级 | 万级 | 十万级 |
消息延迟 | 无 | 微秒级 | 毫秒级 | 毫秒级 |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 非常高(分布式) |
消息丢失 | 低 | 低 | 理论上不会丢失 | 理论上不会丢失 |
文档的完备性 | 高 | 高 | 高 | 高 |
提供快速入门 | 有 | 有 | 有 | 有 |
社区活跃度 | 高 | 高 | 有 | 高 |
商业支持 | 无 | 无 | 商业云 | 商业云 |
JMS中的一些角色
Broker
消息服务器,作为server提供消息核心服务
provider
生产者
消息生产者是由会话创建的一个对象,用于把消息发送到一个目的地。
Consumer
消费者
消息消费者是由会话创建的一个对象,它用于接收发送到目的地的消息。消息的消费可以采用以下两种方法之一:
- 同步消费。通过调用消费者的receive方法从目的地中显式提取消息。receive方法可以一直阻塞到消息到达。
- 异步消费。客户可以为消费者注册一个消息监听器,以定义在消息到达时所采取的动作。
Queue
队列存储,常用与点对点消息模型
默认只能由唯一的一个消费者处理。一旦处理消息删除。
Topic
主题存储,用于订阅/发布消息模型
主题中的消息,会发送给所有的消费者同时处理。只有在消息可以重复处 理的业务场景中可使用。
Queue/Topic都是 Destination 的子接口
ConnectionFactory
连接工厂,jms中用它创建连接
连接工厂是客户用来创建连接的对象,例如ActiveMQ提供的ActiveMQConnectionFactory。
Connection
JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接。
Destination
消息的目的地
目的地是客户用来指定它生产的消息的目标和它消费的消息的来源的对象。JMS1.0.2规范中定义了两种消息传递域:点对点(PTP)消息传递域和发布/订阅消息传递域。 点对点消息传递域的特点如下:
- 每个消息只能有一个消费者。
- 消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,它都可以提取消息。
发布/订阅消息传递域的特点如下:
- 每个消息可以有多个消费者。
- 生产者和消费者之间有时间上的相关性。
- 订阅一个主题的消费者只能消费自它订阅之后发布的消息。JMS规范允许客户创建持久订阅,这在一定程度上放松了时间上的相关性要求 。持久订阅允许消费者消费它在未处于激活状态时发送的消息。
在点对点消息传递域中,目的地被成为队列(queue);在发布/订阅消息传递域中,目的地被成为主题(topic)。
Session
JMS Session是生产和消费消息的一个单线程上下文。会话用于创建消息生产者(producer)、消息消费者(consumer)和消息(message)等。会话提供了一个事务性的上下文,在这个上下文中,一组发送和接收被组合到了一个原子操作中。
代码
概念性的东西官方文档上都有,这里就简单介绍这些,开始介绍ActiveMQ,从下载之后的启动,到简单的发送接收消息。
启动ActiveMQ
下载之后打开ActiveMQ目录,在bin目录下有32位和64位的程序,根据自己需要选择,我这里是64位的,打开之后里面有一个activemq.bat的文件,打开这个文件就启动了。
从启动信息里面可以看到,ActiveMQ的默认端口号是8161
启动成功之后我们可以在浏览器中输入http://localhost:8161看到ActiveMQ的管理界面
点击Manage ActiveMQ broker会弹出一个登陆窗口,账号跟密码默认都是admin,输入之后就可以看到这样的页面了
接下来我们就可以撸代码了
首先创建一个发送消息的客户端,Sender.java
/**
* 消息发送者
*/
public class Sender {
public static void main(String[] args) throws JMSException {
//1、获取连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,//默认用户名
ActiveMQConnectionFactory.DEFAULT_PASSWORD,//默认密码
"tcp://localhost:61616"//连接地址(在启动窗口可以看到tcp连接默认端口号为61616)
);
//2、获取一个向ActiveMQ的连接
Connection connection = activeMQConnectionFactory.createConnection();
//3、获取session
//参数true表示开启事务,false不开启
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//4、存到目的地
Queue queue = session.createQueue("queuename");
//5、向目的地写入消息
//producer-->创建者
//consumer-->消费者
//5.1、创建消息
MessageProducer producer = session.createProducer(queue);
//5.2、向目的地写入消息
for(int i=0;i<100;i++){
TextMessage textMessage = session.createTextMessage("hello"+i);
producer.send(textMessage);
}
//6、关闭连接
session.close();
System.out.println("连接已关闭");
}
}
发送消息的客户端启动之后再看管理界面,点击上面的queue,就可以看到发送的100条消息还没有消费
接下来是一个简单的消息接收者,Receiver.java
/**
* 消息接收者
*/
public class Receiver {
public static void main(String[] args) throws JMSException {
//1、获取连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
"admin",//用户名
"123",//密码
"tcp://localhost:61616"//连接地址
);
//2、获取一个向ActiveMQ的连接
Connection connection = activeMQConnectionFactory.createConnection();
//启动连接
connection.start();
//3、获取session
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//4、存到目的地
Destination queue = session.createQueue("queuename");
//5、获取消息
MessageConsumer consumer = session.createConsumer(queue);
while (true){
TextMessage receive = (TextMessage) consumer.receive();
System.out.println("message:"+receive.getText());
}
}
}
从输出的结果可以看到发送者发送的消息
我们再看ActiveMQ的控制台,已经有了一个消费者
这就是一个简单的生产消费消息的过程了