什么是JMS MQ
全称:Java MessageService 中文:Java 消息服务。
JMS
是Java
的一套API
标准,最初的目的是为了使应用程序能够访问现有的MO
系统(MessageOriented Middleware
),MOM
指的是利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
后来被许多现有的MOM
供应商采用,并实现为MOM
系统。常见MOM
系统包括Apache
的ActiveMQ
、 阿里巴巴的RocketMQ
、IBM
的MQSeries
、Microsoft
的MSMQ
、BEA
的RabbitMQ
等。并非全部的MOM
系统都遵循JMS
规范。
基于JMS
实现的MOM
,又被称为JMSProvider
。
“消息”是在两台计算机间传送的数据单位。消息可以非常简单,例如只包含文本字符串;也可以更复杂,可能包含嵌入对象。 消息被发送到队列中。
“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。
队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。
消息队列的主要特点是异步处理,主要目的是减少请求响应时间和解耦。所以主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时由于使用了消息队列,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦和。
多个应用之间的耦合,由于消息是平台无关和语言无关的,而且语义上也不再是函数调用,因此更适合作为多个应用之间的松耦合的接口。基于消息队列的耦合,不需要发送方和接收方同时在线。 在企业应用集成(EAI)中,文件传输,共享数据库,消息队列,远程过程调用都可以作为集成的方法。 应用内的同步变异步,比如订单处理,就可以由前端应用将订单信息放到队列,后端应用从队列里依次获得消息处理,高峰时的大量订单可以积压在队列里慢慢处理掉。由于同步通常意味着阻塞,而大量线程的阻塞会降低计算机的性能。消息驱动的架构(EDA),系统分解为消息队列,和消息制造者和消息消费者,一个处理流程可以根据需要拆成多个阶段,阶段之间用队列连接起来,前一个阶段处理的结果放入队列,后一个阶段从队列中获取消息继续处理。应用需要更灵活的耦合方式,如发布订阅,比如可以指定路由规则。跨局域网,甚至跨城市的通讯,比如北京机房与广州机房的应用程序的通信。
消息中间件应用场景
1、异步通信
有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
2、缓冲
在任何重要的系统中,都会有需要不同的处理时间的元素。消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。
3、解耦
降低工程间的强依赖程度,针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,当应用发生变化时,可以独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
4、冗余
有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
5、扩展性
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容。
6、可恢复性
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
7、顺序保证
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
8、削峰填谷
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
9、数据流处理
分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。
常用消息队列(ActiveMQ、RabbitMQ、RocketMQ、Kafka)比较
特性MQ | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
生产者消费者模式 | 支持 | 支持 | 支持 | 支持 |
发布订阅模式 | 支持 | 支持 | 支持 | 支持 |
请求回应模式 | 支持 | 支持 | 不支持 | 不支持 |
Api完备性 | 高 | 高 | 高 | 高 |
多语言支持 | 支持 | 支持 | java | 支持 |
单机吞吐量 | 万级 | 万级 | 万级 | 十万级 |
消息延迟 | 无 | 微秒级 | 毫秒级 | 毫秒级 |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 非常高(分布式) |
消息丢失 | 低 | 低 | 理论上不会丢失 | 理论上不会丢失 |
文档的完备性 | 高 | 高 | 高 | 高 |
提供快速入门 | 有 | 有 | 有 | 有 |
社区活跃度 | 高 | 高 | 有 | 高 |
商业支持 | 无 | 无 | 商业云 | 商业云 |
JMS中的一些角色
1、Broker:消息服务器,作为server提供消息核心服务
2、provider:生产者,消息生产者是由会话创建的一个对象,用于把消息发送到一个目的地。
3、Consumer:消费者,消息消费者是由会话创建的一个对象,它用于接收发送到目的地的消息。消息的消费可以采用以下两种方法之一:
-
同步消费。通过调用消费者的receive方法从目的地中显式提取消息。receive方法可以一直阻塞到消息到达。
-
异步消费。客户可以为消费者注册一个消息监听器,以定义在消息到达时所采取的动作。
4、p2p:基于点对点的消息模型
消息生产者生产消息发送到 queue 中,然后消息消费者从 queue 中取出并且消费消息。 消息被消费以后,queue 中不再有存储,所以消息消费者不可能消费到已经被消费的消
息。
Queue 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费、其它 的则不能消费此消息了。 当消费者不存在时,消息会一直保存,直到有消费消费
5、pub/sub:基于订阅/发布的消息模型
消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消息。
和点对点方式不同,发布到 topic 的消息会被所有订阅者消费。 当生产者发布消息,不管是否有消费者。都不会保存消息 一定要先有消息的消费者,后有消息的生产者。
6、PTP 和 PUB/SUB 简单对
1 | Topic | Queue |
---|---|---|
- | Publish Subscribe messaging 发布 订阅消息 | Point-to-Point 点对点 |
有无状态 | topic 数据默认不落地,是无状态的。 | Queue 数据默认会在 mq 服 务器上以文件形式保存,比如 Active MQ 一 般 保 存 在 $AMQ_HOME\data\kahadb 下 面。也可以配置成 DB 存储。 |
完整性保障 | 并不保证 publisher 发布的每条数 据,Subscriber 都能接受到。 | Queue 保证每条数据都能 被 receiver 接收。消息不超时。 |
消息是否会丢失 | 一般来说 publisher 发布消息到某 一个 topic 时,只有正在监听该 topic 地址的 sub 能够接收到消息;如果没 有 sub 在监听,该 topic 就丢失了。 | Sender 发 送 消 息 到 目 标 Queue, receiver 可以异步接收这 个 Queue 上的消息。Queue 上的 消息如果暂时没有 receiver 来 取,也不会丢失。前提是消息不 超时。 |
消息发布接 收策略 | 一对多的消息发布接收策略,监 听同一个topic地址的多个sub都能收 到 publisher 发送的消息。Sub 接收完 通知 mq 服务器 | 一对一的消息发布接收策 略,一个 sender 发送的消息,只 能有一个 receiver 接收。 receiver 接收完后,通知 mq 服务器已接 收,mq 服务器对 queue 里的消 息采取删除或其他操作。 |
7、Queue:队列存储,常用与点对点消息模型,默认只能由唯一的一个消费者处理。一旦处理消息删除。
8、Topic:主题存储,用于订阅/发布消息模型,主题中的消息,会发送给所有的消费者同时处理。只有在消息可以重复处 理的业务场景中可使用。Queue
/Topic
都是Destination
的子接口
9、ConnectionFactory:连接工厂,jms中用它创建连接,连接工厂是客户用来创建连接的对象,例如ActiveMQ提供的ActiveMQConnectionFactory。
10、Connection:JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接。
11、Destination:消息的目的地,目的地是客户用来指定它生产的消息的目标和它消费的消息的来源的对象。JMS1.0.2规范中定义了两种消息传递域:点对点(PTP)消息传递域和发布/订阅消息传递域。 点对点消息传递域的特点如下:
-
每个消息只能有一个消费者。
-
消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,它都可以提取消息。
发布/订阅消息传递域的特点如下:
-
每个消息可以有多个消费者。
-
生产者和消费者之间有时间上的相关性。
-
订阅一个主题的消费者只能消费自它订阅之后发布的消息。JMS规范允许客户创建持久订阅,这在一定程度上放松了时间上的相关性要求 。持久订阅允许消费者消费它在未处于激活状态时发送的消息。
在点对点消息传递域中,目的地被成为队列(queue);在发布/订阅消息传递域中,目的地被成为主题(topic)。
12、Session:JMS Session
是生产和消费消息的一个单线程上下文。会话用于创建消息生产者producer
、消息消费者consumer
和消息message
等。会话提供了一个事务性的上下文,在这个上下文中,一组发送和接收被组合到了一个原子操作中。
JMS的消息格式
JMS消息由以下三部分组成的:
-
消息头: 每个消息头字段都有相应的getter和setter方法。
-
消息属性: 如果需要除消息头字段以外的值,那么可以使用消息属性。
-
消息体: JMS定义的消息类型有TextMessage、MapMessage、BytesMessage、StreamMessage和ObjectMessage。
消息分类:
-
TextMessage:文本消息
-
MapMessage:k/v
-
BytesMessage:字节流
-
StreamMessage:java原始的数据流
-
ObjectMessage:序列化的java对象
消息可靠性机制
确认JMS消息
只有在被确认之后,才认为已经被成功地消费了。
消息的成功消费通常包含三个阶段:客户接收消息、客户处理消息和消息被确认。
在事务性会话中,当一个事务被提交的时候,确认自动发生。
在非事务性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode)。该参数有以下三个可选值:
-
Session.AUTO_ACKNOWLEDGE。当客户成功的从receive方法返回的时候,或者从MessageListener.onMessage方法成功返回的时候,会话自动确认客户收到的消息。
-
Session.CLIENT_ACKNOWLEDGE。客户通过消息的acknowledge方法确认消息。需要注意的是,在这种模式中,确认是在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消费的消息。例如,如果一个消息消费者消费了10个消息,然后确认第5个消息,那么所有10个消息都被确认。
-
Session.DUPS_ACKNOWLEDGE。该选择只是会话迟钝的确认消息的提交。如果JMS Provider失败,那么可能会导致一些重复的消息。如果是重复的消息,那么JMS Provider必须把消息头的JMSRedelivered字段设置为true。
持久性
JMS 支持以下两种消息提交模式:
-
PERSISTENT。指示JMS Provider持久保存消息,以保证消息不会因为JMS Provider的失败而丢失。
-
NON_PERSISTENT。不要求JMS Provider持久保存消息。
优先级
可以使用消息优先级来指示JMS Provider首先提交紧急的消息。优先级分10个级别,从0(最低)到9(最高)。如果不指定优先级,默认级别是4。需要注意的是,JMS Provider并不一定保证按照优先级的顺序提交消息。
消息过期
可以设置消息在一定时间后过期,默认是永不过期。
临时目的地
可以通过会话上的createTemporaryQueue方法和createTemporaryTopic方法来创建临时目的地。它们的存在时间只限于创建它们的连接所保持的时间。只有创建该临时目的地的连接上的消息消费者才能够从临时目的地中提取消息。
持久订阅
首先消息生产者必须使用PERSISTENT提交消息。客户可以通过会话上的createDurableSubscriber方法来创建一个持久订阅,该方法的第一个参是一个topic,第二个参数是订阅的名称。JMS Provider会存储发布到持久订阅对应的topic上的消息。如果最初创建持久订阅的客户或者任何其它客户使用相同的连接工厂和连接的客户ID、相同的主题和相同的订阅名再次调用会话上的createDurableSubscriber方法,那么该持久订阅就会被激活。JMS Provider会向客户发送客户处于非激活状态时所发布的消息。 持久订阅在某个时刻只能有一个激活的订阅者。持久订阅在创建之后会一直保留,直到应用程序调用会话上的unsubscribe方法。
本地事务
在一个JMS客户端,可以使用本地事务来组合消息的发送和接收。JMS Session接口提供了commit和rollback方法。事务提交意味着生产的所有消息被发送,消费的所有消息被确认;事务回滚意味着生产的所有消息被销毁,消费的所有消息被恢复并重新提交,除非它们已经过期。 事务性的会话总是牵涉到事务处理中,commit或rollback方法一旦被调用,一个事务就结束了,而另一个事务被开始。关闭事务性会话将回滚其中的事务。 需要注意的是,如果使用请求/回复机制,即发送一个消息,同时希望在同一个事务中等待接收该消息的回复,那么程序将被挂起,因为知道事务提交,发送操作才会真正执行。
需要注意:消息的生产和消费不能包含在同一个事务中。
ActiveMQ
Broker
ActiveMQ 5.0
的二进制发布包中bin
目录中包含一个名为activemq
的脚本,直接运行这个脚本就可以启动一个broker。 此外也可以通过Broker Configuration URI
或Broker XBean URI
对broker
进行配置,以下是一些命令行参数的例子:
Example | Description |
---|---|
activemq | Runs a broker using the default ‘xbean:activemq.xml’ as the broker configuration file. |
activemq xbean:myconfig.xml | Runs a broker using the file myconfig.xml as the broker configuration file that is located in the classpath. |
activemq xbean:file:./conf/broker1.xml | Runs a broker using the file broker1.xml as the broker configuration file that is located in the relative file path ./conf/broker1.xml |
activemq xbean:file:C:/ActiveMQ/conf/broker2.xml | Runs a broker using the file broker2.xml as the broker configuration file that is located in the absolute file path C:/ActiveMQ/conf/broker2.xml |
activemq broker:(tcp://localhost:61616, tcp://localhost:5000)?useJmx=true | Runs a broker with two transport connectors and JMX enabled. |
activemq broker:(tcp://localhost:61616, network:tcp://localhost:5000)?persistent=false | Runs a broker with 1 transport connector and 1 network connector with persistence disabled. |
存储
KahaDB存储
KahaDB
是默认的持久化策略,所有消息顺序添加到一个日志文件中,同时另外有一个索引文件记录指向这些日志的存储地址,还有一个事务日志用于消息回复操作。是一个专门针对消息持久化的解决方案,它对典型的消息使用模式进行了优化。
在data/kahadb
这个目录下,会生成四个文件,来完成消息持久化
-
db.data
: 它是消息的索引文件,本质上是B-Tree
(B树),使用B-Tree
作为索引指向db-*.log
里面存储的消息. -
db.redo
: 用来进行消息恢复. -
db-.log
: 存储消息内容。新的数据以APPEND
的方式追加到日志文件末尾。属于顺序写入,因此消息存储是比较快的。默认是32M
,达到阈值会自动递增. -
lock
文件: 写入当前获得kahadb
读写权限的broker
,用于在集群环境下的竞争处理.
可以在配置文件中修改数据存储文件的大小:
<persistenceAdapter>
<!--directory:保存数据的目录;journalMaxFileLength:保存消息的文件大小 -->
<kahaDBdirectory="${activemq.data}/kahadb"journalMaxFileLength="1024mb"/>
</persistenceAdapter>
特性:
-
日志形式存储消息;
-
消息索引以
B-Tree
结构存储,可以快速更新; -
完全支持
JMS
事务; -
支持多种恢复机制
kahadb
可以限制每个数据文件的大小。不代表总计数据容量。
AMQ 方式
只适用于5.3
版本之前。 AMQ
也是一个文件型数据库,消息信息最终是存储在文件中。内存中也会有缓存数据。
<persistenceAdapter>
<!--directory:保存数据的目录 ;maxFileLength:保存消息的文件大小 -->
<amqPersistenceAdapterdirectory="${activemq.data}/amq" maxFileLength="512mb"/>
</persistenceAdapter>
性能高于JDBC
,写入消息时,会将消息写入日志文件,由于是顺序追加写,性能很高。为了提升性能,创建消息主键索引,并且提供缓存机制,进一步提升性能。每个日志文件的大小都是有限制的(默认32m
,可自行配置)。 当超过这个大小,系统会重新建立一个文件。当所有的消息都消费完成,系统会删除这个文件或者归档。
主要的缺点是AMQ Message
会为每一个Destination
创建一个索引,如果使用了大量的Queue
,索引文件的大小会占用很多磁盘空间。
而且由于索引巨大,一旦Broker
(ActiveMQ
应用实例)崩溃,重建索引的速度会非常慢。
虽然AMQ
性能略高于KahaDB
方式,但是由于其重建索引时间过长,而且索引文件 占用磁盘空间过大,所以已经不推荐使用。
JDBC存储
使用JDBC
持久化方式,数据库默认会创建3
个表,每个表的作用如下:
-
activemq_msgs
:queue和topic的消息都存在这个表中 -
activemq_acks
:存储持久订阅的信息和最后一个持久订阅接收的消息ID -
activemq_lock
:跟kahadb
的lock
文件类似,确保数据库在某一时刻只有一个broker
在访问
ActiveMQ
将数据持久化到数据库中。可以使用任意的数据库中。
本环节中使用MySQL
数据库。 下述文件为activemq.xml
配置文件部分内容。
- 首先定义一个
mysql-ds
的MySQL
数据源,然后在persistenceAdapter
节点中配置jdbcPersistenceAdapter
并且引用刚才定义的数据源。
dataSource
指定持久化数据库的bean
,createTablesOnStartup
是否在启动的时候创建数据表,默认值是true
,这样每次启动都会去创建数据表了,一般是第一次启动的时候设置为true
,之后改成false
。
1、在activmq配置文件的Beans中添加
<bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true" />
<property name="username" value="root" />
<property name="password" value="12345678" />
<property name="maxActive" value="200" />
<property name="poolPreparedStatements" value="true" />
</bean>
2、修改persistenceAdapter
<persistenceAdapter>
<!-- <kahaDB directory="${activemq.data}/kahadb"/> -->
<jdbcPersistenceAdapter dataSource="#mysql-ds" createTablesOnStartup="true" />
</persistenceAdapter>
这里需要注意,由于jdbc
相关的包activemq
默认没有带,所以需要以下jar
包,放到activmq_home/libs
下
mysql中表字段解释
activemq_acks:用于存储订阅关系。如果是持久化Topic
,订阅者和服务器的订阅关系在这个表保存。
主要的数据库字段如下:
-
container
:消息的destination
-
sub_dest
:如果是使用static
集群,这个字段会有集群其他系统的信息 -
client_id
:每个订阅者都必须有一个唯一的客户端id
用以区分 -
sub_name
:订阅者名称 -
selector
:选择器,可以选择只消费满足条件的消息。条件可以用自定义属性实现,可支持多属性and
和or
操作 -
last_acked_id
:记录消费过的消息的id
activemq_lock:在集群环境中才有用,只有一个Broker
可以获得消息,称为Master Broker
,其他的只能作为备份等待Master Broker
不可用,只有Master Broker
不可用了,其他节点才可能成为下一个Master Broker
。这个表用于记录哪个Broker
是当前的Master Broker
。
activemq_msgs:用于存储消息,Queue
和Topic
都存储在这个表中。主要字段:
-
id
:自增的数据库主键 -
container
:消息的destination
-
msgid_prod
:消息发送者客户端的主键 -
msg_seq
:是发送消息的顺序,msgid_prod
+msg_seq
可以组成jms
的messageid
-
expiration
:消息的过期时间,存储的是从1970-01-01
到现在的毫秒数 -
msg
:消息本体的java
序列化对象的二进制数据 -
priority
:优先级,从0-9
,数值越大优先级越高,默认为4
-
xid
:用于存储订阅关系。如果是持久化topic
,订阅者和服务器的订阅关系在这个表保存。
LevelDB存储
LevelDB
持久化性能高于KahaDB
,虽然目前默认的持久化方式仍然是KahaDB
。并且,在ActiveMQ 5.9
版本提供了基于LevelDB
和Zookeeper
的数据复制方式,用于master-slave
方式的首选数据复制方案。
Memory 消息存储
顾名思义,基于内存的消息存储,就是消息存储在内存中。persistent=”false”,表示不设置持久化存储,直接存储到内存中。
JDBC Message store with ActiveMQ Journal
这种方式克服了JDBC Store
的不足,JDBC
存储每次消息过来,都需要去写库和读库。 ActiveMQ Journal
使用延迟存储数据到数据库,当消息来到时先缓存到文件中,延迟后才写到数据库中。
当消费者的消费速度能够及时跟上生产者消息的生产速度时,journal
文件能够大大减少需要写入到DB
中的消息。 举个例子,生产者生产了10000
条消息,这10000
条消息会保存到journal
文件,如果消费者的消费速度很快的情况下,在journal
文件还没有同步到DB
之前,消费者已经消费了99%
的以上的消息,那么这个时候只需要同步剩余的1%
的消息到DB
。如果消费者的消费速度很慢,这个时候journal
文件可以使消息以批量方式写到DB
。
协议
官方网站列出了支持的所有协议
ActiveMQ
支持的client-broker
通讯协议有: TCP
、NIO
、UDP
、SSL
、Http(s)
、VM
。
Transmission Control Protocol (TCP)
- 这是默认的
Broker
配置,TCP
的Client
监听端口是61616
。 - 在网络传输数据前,必须序列化数据,消息是通过一个叫
wire protocol
的来序列化成字节流。默认情况下,ActiveMQ
把wire protocol
叫做OpenWire
,它的目的是促使网络上的效率和数据快速交互。 TCP
连接的URI
形式:tcp://hostname:port?key=value&key=value
,参数可选- TCP传输的优点:
- TCP协议传输可靠性高,稳定性强
- 高效性:字节流方式传递,效率很高
- 有效性、可用性:应用广泛,支持任何平台
可以通过修改activemq.xml配置文件开启各种协议支持
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
New I/O API Protocol(NIO)
-
NIO
协议和TCP
协议类似,但NIO
更侧重于底层的访问操作。它允许开发人员对同一资源可有更多的client
调用和服务端有更多的负载。 -
NIO
连接的URI形式:nio://hostname:port?key=value
,其中参数可选 -
适合使用NIO协议的场景:
-
如果有大量的
Client
去链接到Broker
,会被操作系统的线程数所限制的。因此,NIO
的实现比TCP
需要更少的线程去运行,所以建议使用NIO
协议 -
可能对于
Broker
有一个很迟钝的网络传输NIO
比TCP
提供更好的性能
配置方式:
<transportConnectors>
<transportConnector name="tcp" uri="tcp://localhost:61616?trace=true" />
<transportConnector name="nio" uri="nio://localhost:61618?trace=true" />
</transportConnectors>
上面的配置,示范了一个TCP协议监听61616端口,一个NIO协议监听61618端口
User Datagram Protocol(UDP)
UDP
和TCP
的区别:
-
TCP
是一个原始流的传递协议,意味着数据包是有保证的,换句话说,数据包是不会被复制和丢失的。UDP
不会保证数据包的传递的 -
TCP
也是一个稳定可靠的数据包传递协议,意味着数据在传递的过程中不会被丢失。这样确保了在发送和接收之间能够可靠的传递。相反,UDP
仅仅是一个链接协议
TCP
是被用在稳定可靠的场景中使用的;UDP
通常用在快速数据传递和不怕数据丢失的场景中,ActiveMQ
通过防火墙时,只能用UDP
UDP
连接的URI
形式:udp://hostname:port?key=value
配置方式:
<transportConnectors>
<transportConnector name="udp" uri="udp://localhost:61618?trace=true" />
</transportConnectors>
Secure Sockets Layer Protocol (SSL)
连接的URI形式:ssl://hostname:port?key=value
配置示例:
<transportConnectors>
<transportConnector name="ssl" uri="ssl://localhost:61617?trace=true"/>
</transportConnectors>
Hypertext Transfer Protocol (HTTP/HTTPS)
像web
和email
等服务需要通过防火墙来访问的,Http
可以使用这种场合
连接的URI
形式:http://hostname:port?key=value
或者https://hostname:port?key=value
配置示例:
<transportConnectors>
<transportConnector name="http" uri="http://localhost:8080?trace=true" />
</transportConnectors>
VM Protocol(VM)
VM transport
允许在VM
内部通信,从而避免了网络传输的开销。这时候采用的连接不是socket
连接,而是直接的方法调用。
第一个创建VM
连接的客户会启动一个embed VM broker
,接下来所有使用相同的broker name
的VM
连接都会使用这个broker
。当这个broker
上所有的连接都关闭的时候,这个broker
也会自动关闭。
连接的URI形式:vm://brokerName?key=value
Java中嵌入的方式:vm:broker:(tcp://localhost:6000)?brokerName=embeddedbroker&persistent=false
,定义了一个嵌入的broker
名称为embededbroker
以及配置了一个tcpTransprotConnector
在监听端口6000
使用一个加载一个配置文件来启动broker
vm://localhost?brokerConfig=xbean:activemq.xml
简单案例
下载
apache-activemq-5.16.3-bin.tar.gz
安装启动
解压后直接执行: bin/activemq start
web控制台
Web GUI访问地址: http://localhost:8161
修改访问端口
修改ActiveMQ
配置文件: ${activemq_home}/conf/jetty.xml
jettyport节点
配置文件修改完毕,保存并重新启动 ActiveMQ 服务。
开发
pom依赖
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-client</artifactId>
<version>5.16.3</version>
</dependency>
Sender
package com.zjw.activemq.simple;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
* @author zjwblog <co.zjwblog@gmail.com>
* @version 1.0
* @date 2021/12/12 7:19 下午
*/
public class Sender {
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
"failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)?Randomize=false"
);
Connection conn = factory.createConnection();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination queue = session.createQueue("user");
MessageProducer producer = session.createProducer(queue);
for (int i = 0; i < 20; i++) {
TextMessage message = session.createTextMessage("msg-" + i);
producer.send(message);
}
conn.close();
}
}
Receiver
package com.zjw.activemq.simple;
import java.util.concurrent.TimeUnit;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
* @author zjwblog <co.zjwblog@gmail.com>
* @version 1.0
* @date 2021/12/12 7:31 下午
*/
public class Receiver {
public static void main(String[] args) {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
"failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)?Randomize=false"
);
Connection conn = null;
try {
conn = factory.createConnection();
conn.start();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination queue = session.createQueue("user");
MessageConsumer consumer = session.createConsumer(queue);
while (true) {
TextMessage msg = (TextMessage) consumer.receive();
TimeUnit.MILLISECONDS.sleep((int)(Math.random()*2000));
System.out.println("received message: " + msg.getText());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != conn) {
try {
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
安全机制
web控制台安全
${activemq_home}/conf/jetty-realm.properties
中配置了用户名:密码,角色
# username: password [,rolename ...]
admin: admin, admin
user: user, user
需要注意格式:用户名:密码,角色
,配置需重启ActiveMQ才会生效。
消息安全机制
修改 activemq.xml
在123行</broker>
节点中添加simpleAuthenticationPlugin
<plugins>
<simpleAuthenticationPlugin>
<users>
<authenticationUser username="admin" password="admin" groups="admins,publishers,consumers"/>
<authenticationUser username="publisher" password="publisher" groups="publishers,consumers"/>
<authenticationUser username="consumer" password="consumer" groups="consumers"/>
<authenticationUser username="guest" password="guest" groups="guests"/>
</users>
</simpleAuthenticationPlugin>
</plugins>
这里配置之后,在Coding
的时候就需要配置用户名和密码了,否则无法连接到broker
。