JMS, ActiveMQ 学习一则

2020-05-21

JMS标准

JMS(Java Messaging Service)是Java平台上有关面向消息中间件(MOM)的技术规范, 它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生, 发送, 接收消息的接口简化企业应用的开发, 翻译为Java消息服务;

ORACLE 官方介绍页面

消息传递模型 (Message Delivery Models)

JMS supports two different message delivery(投送) models:

  1. Point-to-Point (Queue destination): In this model, a message is delivered from a producer to one consumer. The messages are delivered to the destination, which is a queue, and then delivered to one of the consumers registered for the queue. While any number of producers can send messages to the queue, each message is guaranteed to be delivered, and consumed by one consumer. If no consumers are registered to consume the messages, the queue holds them until a consumer registers to consume them.

简而言之, 点对点,保证每条消息只有一个消费者消费;接收者在成功接收消息之后需向队列应答成功, 如果没有注册消费者的消息,将在队列保存; 如果希望发送的每个消息都应该被成功处理的话, 那么选择P2P模型

  1. Publish/Subscribe (Topic destination): In this model, a message is delivered from a producer to any number of consumers. Messages are delivered to the topic destination, and then to all active consumers who have subscribed to the topic. In addition, any number of producers can send messages to a topic destination, and each message can be delivered to any number of subscribers. If there are no consumers registered, the topic destination doesn't hold messagesunless it has durable subscription for inactive consumers. A durable subscription represents a consumer registered with the topic destination that can be inactive at the time the messages are sent to the topic.

简而言之, Publish/Subscribe, 类似MQTT, 如果没有注册消费者的消息,将不会保存 (除非长期订阅者?)

Publish/Subscribe 它是有时间性限制, 就是对一个topic, 必须有一个订阅者在激活(运行)时才允许发送消息? 为了解决这个问题, JMS 允许创建一个持久化的订阅者..

程序模型/消息体 (The JMS Programming Model)

A JMS application consists of a set of application-defined messages and a set of clients that exchange them. JMS clients interact by sending and receiving messages using the JMS API.
A message is composed of three parts: header, properties, and a body.(一个消息由三部分组成: 头部, 属性和主体; )

The header, which is required for every message(每一个消息头部是必须的),
contains information that is used for routing and identifying messages. Some of these fields are set automatically, by the JMS provider, during producing and delivering a message, and others are set by the client on a message by message basis.

Properties, which are optional, provide values that clients can use to filter messages. ...

The body, which is also optional, contains the actual data to be exchanged.

The JMS specification defined six type or classes of messages that a JMS provider must support(JMS规定 必须支持6种类型):

  • Message: This represents a message without a message body.(表示没有消息体)
  • StreamMessage: A message whose body contains a stream of Java primitive types. It is written and read sequentially.(流类型的消息?)
  • MapMessage: A message whose body contains a set of name/value pairs. The order of entries is not defined.(键/值类型, 未定义顺序的)
  • TextMessage: A message whose body contains a Java string...such as an XML message.(文本类型, 应该是用java的String接受)
  • ObjectMessage: A message whose body contains a serialized Java object.(对象类型, 可序列化为java对象的)
  • BytesMessage: A message whose body contains a stream of uninterpreted bytes.(byte[] 类型)

生产和消费消息 (Producing and Consuming Messages)

官方API Maven依赖

<!-- https://mvnrepository.com/artifact/javax.jms/jms -->
<dependency>
    <groupId>javax.jms</groupId>
    <artifactId>jms</artifactId>
    <version>1.1</version>
</dependency>

官方只定义API规范, 并没有实现..

生产消息 客户端 (Client to Produce Messages )

  1. Use the Java Naming and Directory Interface (JNDI) to find a ConnectionFactory object(使用JNDI查找一个ConnectionFactory?), or instantiate a ConnectionFactory object directly and set its attributes.(或者直接实例ConnectionFactory对象并设置它的属性)
Context ctx = new InitialContext();
ConnectionFactory cf1 = (ConnectionFactory) ctx.lookup("jms/QueueConnectionFactory");
ConnectionFactory cf2 = (ConnectionFactory) ctx.lookup("/jms/TopicConnectionFactory");
  1. Use the ConnectionFactory object to create a Connection object. This can be done as follows:

Connection connection = connFactory.createConnection();

  1. Use the Connection object to create one or more Session objects (一个Session 一个连接?), which provide transactional context with which to group a set of sends and receives into an atomic unit of work.事务? A session can be created as follows:
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)

The createSession() method takes two arguments: the first (false in this case) means that the session is not transacted,(第一个参数 false 即该会话是非事务) and the second means that the session will automatically acknowledge messages when they have been received successfully.(第二个参数 是自动确认接受消息, 在第一个参数为false是才有意义)

  1. Use JNDI to find Destination object(s), or instantiate one directly and configure it by setting its attributes.(不知所云)
    应该是通过查找 System.getProperty的实现类来加载?
System.setProperty("java.naming.factory.initial", "org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory");
System.setProperty("connectionFactory.invmConnectionFactory", "tcp://127.0.0.1:61616");

A destination object is used by the client to specify the source of messages it consumes and the target of messages it produces. (客户端使用 Destination对象来指定它使用的消息源和它生成的消息目标; )

In the point-to-point messaging, destinations are known as queues, and in the publish/subscribe model of messaging, they are known as topics.(在点对点消息传递中, 目的地称为Queue, 而在消息传递的发布/订阅模型中, 它们称为Topic; )

简而言之 P2P模式中 生产者和消费者都是使用 Destination作为操作对象; 而 Publish/Subscribe模式中使用 Topic 作为操作对象

  1. you can create a MessageProducer object without specifying a Destination object(给定Destination 创建生产者), but in that case a Destination object must be specified for each message produced.
    MessageProducer producer = session.createProducer(SomeQueue OR SomeTopic);

Once a producer has been created, it can be used to send messages as follows:

producer.send(message);

消费消息 客户端 (Client to Consume Messages)

The first four steps are the same as above.(前四个步骤与上述相同; )

  1. Use a Session object and a Destination object to create any needed MessageConsumer objects that are used for receiving messages. This can be done as follows:

MessageConsumer consumer = session.createConsumer(SomeQueue or SomeTopic);

Note that if topic is being used, then you can use the Session.createDurableSubscriber() to create a durable topic subscriber.(如果是topic模式, 使用Session.createDurableSubscriber()创建持久主题订阅者; )

Once the consumer has been created, it can be used to receive messages. Message delivery, however, doesn't begin until you start the connection created earlier, which can be done by calling the start() method:(创建使用者后, 便可以使用它来接收消息; 但是, 要开始连接后, 消息传递才开始, 可以通过调用start() )

connection.start();
Message msg = consumer.receive();

同步接受

可以使用 Message msg = consumer.receive(1500L);指定超时时间;

异步接受

If asynchronous communication is desired, instantiate a MessageListener object and register it with a MessageConsumer object.(如果需要异步通信, 请实例化 MessageListener 对象并将其注册到 MessageConsumer 对象; )

A MessageListener object acts as an asynchronous event handler for messages. The MessageListener interface contains one method, onMessage(), which you implement to receive and process the messages.

MessageListener listener = new MyListener();
consumer.setMessageListener(listener);
  1. Instruct the Connection object to start delivery of messages by calling the start() method.(通过调用Connection的start() 开始消息传递 )

消息的: 事务, 签收,持久性

事务

  • Transacction 事务, 生产端提交时的事务
    false:
    只要执行send,就进入到队列中。
    关闭事务,那第2个签收参数的设置需要有效
    true:
    执行send再执行commit,消息才被真正的提交到队列中(session.commit() session.rolllback())
    消息需要批量发送,需要缓冲区处理
    事务偏生产者/签收偏消费者

签收

  • Acknowledge:签收,客户端接受
    非事务:
  • 自动签收(默认):Session.AUTO_ACKNOWLEDGE
  • 手动签收:Session.CLIENT_ACKNOWLEDGE 客户端调用acknowledge()方法手动签收
  • 允许重复消息: Session.DUPS_OK_ACKNOWLEDGE

持久性

PERSISTENT:持久性

  • 参数设置(队列默认持久)
    非持久:messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
    非持久化:当服务器宕机,消息不存在
    持久:messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
    持久化:当服务器宕机,消息依然存在
  • 持久性的Queue
  • 持久的Topic

小结/代码


import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;

public class App 
{
    public static void main( String[] args ) throws Exception
    {
        Context ctx = new InitialContext();
        ConnectionFactory cf1 = (ConnectionFactory) ctx.lookup("jms/QueueConnectionFactory");
        Connection connection = cf1.createConnection();
        //第一个参数 是否开启事务, 第二个参数, 应答方式; 在第一个参数为false时, 才有意义, 参考JMS的API
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        
        //P2P的主要操作对象; 
        Destination dest = (Queue) ctx.lookup("jms/SomeQueue");
        
        //生产者
        MessageProducer producer = session.createProducer(dest);
        //创建发送消息
        TextMessage msg = session.createTextMessage();
        msg.setText("Hello there!");
        producer.send(msg);
        
        //消费者
        MessageConsumer consumer = session.createConsumer(dest);
        
        //接受消息
        Message msg2 = consumer.receive(1500L);
        
        //异步接受
        consumer.setMessageListener(new MessageListener() {
			@Override
			public void onMessage(Message arg0) {
				// TODO Auto-generated method stub
			}
		});
        
    }
}



以上代码是JMS 标准API实例; 但需要实现, 并不能跑起来.. 具体实现文档使用的是Sun Java System Message Queue 3.5,还有一顿安装操作, jndi配置文件, 我去..

ActiveMQ

Apache ActiveMQ™ is the most popular open source, multi-protocol, Java-based messaging server.

It supports industry standard protocols so users get the benefits of client choices across a broad range of languages and platforms.(它支持行业标准协议, 因此用户可以通过多种语言和平台从客户选择中受益; )

Connectivity from C, C++, Python, .Net, and more is available.

官页
官页下载 Artemis 版
官方文档

性能不够牛逼? 还得看 [[N_Kafka]]

关于 Artemis版, 就是更新较大的版本, 取个名称? 也就是说目前的ActiveMQ是5.x, Artemis将是6.x

注意API/文档都是不同的, 坑...

一般概念 (General Concepts

Messaging systems allow you to loosely couple heterogeneous systems together, whilst typically providing reliability, transactions and many other features.

Unlike systems based on a Remote Procedure Call (RPC) pattern(与基于远程过程调用(RPC)模式的系统不同),
messaging systems primarily use an asynchronous message passing pattern with no tight relationship between requests and responses. Most messaging systems also support a request-response mode but this is not a primary feature of messaging systems.(消息传递系统主要使用异步消息传递模式, 请求和响应之间没有紧密的关系; 大多数邮件系统还支持请求-响应模式, 但这不是邮件系统的主要功能; )

JMS (Java Message Service

JMS is part of Oracle's Java EE specification. It's a Java API that encapsulates both message queue and publish-subscribe messaging patterns. JMS is a lowest common denominator specification - i.e. it was created to encapsulate common functionality of the already existing messaging systems that were available at the time of its creation.

JMS does not define a standard wire format - it only defines a programmatic API so JMS clients and servers from different vendors cannot directly interoperate since each will use the vendor's own internal wire protocol.

Apache ActiveMQ Artemis provides a fully compliant JMS 1.1 and JMS 2.0 client implementation.

简而言之, Oracle 定义了API 标准, 但没定义协议...

见上JMS标准

支持的协议

简而言之, ActiveMQ 上层提供JMS的标准API, 下层消息传输可以使用任意的协议

MQTT
MQTT is a lightweight connectivity protocol. It is designed to run in environments where device and networks are constrained. Out of the box Apache ActiveMQ Artemis supports version MQTT 3.1.1. Any client supporting this version of the protocol will work against Apache ActiveMQ Artemis.
AMQP
OpenWire
STOMP
HornetQ (for use with HornetQ clients).
Core (Artemis CORE protocol)

服务器使用 (Using the Server )

(Using the Server)

下载页 Artemis版

After downloading the distribution, the following highlights some important folders on the distribution:

|___ bin
|
|___ examples
|      |___ common
|      |___ features
|      |___ perf
|      |___ protocols
|
|___ lib
|      |___ client
|
|___ schema
|
|___ web
    |___ api
    |___ hacking-guide
    |___ migration-guide
    |___ user-manual

bin - binaries and scripts needed to run ActiveMQ Artemis.

examples - All manner of examples. Please refer to the examples chapter for details on how to run them.

lib - jars and libraries needed to run ActiveMQ Artemis

schema - XML Schemas used to validate ActiveMQ Artemis configuration files(用于验证ActiveMQ Artemis XML配置文件的xsd检验文件)

web - The folder where the web context is loaded when the broker runs.(broker web根目录)

api - The api documentation is placed under the web folder.(API文档)

user-manual - The user manual is placed under the web folder.(用户手册)

一些概念可参考: [[N_Kafka]]

创建Broker 实例 (Creating a Broker Instance)

建议另外新建一个目录, 存放Broker Instance的数据

${ARTEMIS_HOME}/bin/artemis create mybroker --data F:\active-mq-work

帮助参考: ./artemis help create

PS F:\active-mq-work> artemis create mybroker
Creating ActiveMQ Artemis instance at: F:\active-mq-work\mybroker

--user: is a mandatory property!
Please provide the default username:
admin

--password: is mandatory with this configuration:
Please provide the default password:


--allow-anonymous | --require-login: is a mandatory property!
Allow anonymous access?, valid values are Y,N,True,False
Y

Auto tuning journal ...
done! Your system can make 0.06 writes per millisecond, your journal-buffer-timeout will be 16300000

You can now start the broker by executing:

   "F:\active-mq-work\mybroker\bin\artemis" run

Or you can setup the broker as Windows service and run it in the background:

   "F:\active-mq-work\mybroker\bin\artemis-service.exe" install
   "F:\active-mq-work\mybroker\bin\artemis-service.exe" start

   To stop the windows service:
      "F:\active-mq-work\mybroker\bin\artemis-service.exe" stop

   To uninstall the windows service
      "F:\active-mq-work\mybroker\bin\artemis-service.exe" uninstall

A broker instance directory will contain the following sub directories:

holds execution scripts associated with this instance.
etc: hold the instance configuration files (包含配置文件)
data: holds the data files used for storing persistent messages (数据文件/持久化的消息数据)
log: holds rotating log files
tmp: holds temporary files that are safe to delete between broker runs

///
"F:\active-mq-work\mybroker\bin\artemis.cmd" run

之后该文件夹, 就是一个完全独立的实例了

开始和停止broker实例 (Starting and Stopping a Broker Instance)

Assuming you created the broker instance under /var/lib/mybroker all you need to do start running the broker instance is execute:

/var/lib/mybroker/bin/artemis run

2020-06-08 14:05:54,453 INFO  [org.apache.activemq.artemis] AMQ241001: HTTP Server started at http://localhost:8161
2020-06-08 14:05:54,453 INFO  [org.apache.activemq.artemis] AMQ241002: Artemis Jolokia REST API available at http://localhost:8161/console/jolokia
2020-06-08 14:05:54,454 INFO  [org.apache.activemq.artemis] AMQ241004: Artemis Console available at http://localhost:8161/console

打开http://localhost:8161/console, 用创建时的用户名密码即可访问到管理页面

To stop the Apache ActiveMQ Artemis instance you will use the same artemis script, but with the stop argument. Example:

/var/lib/mybroker/bin/artemis stop

地址模型(Addressing Model)

(http://activemq.apache.org/components/artemis/documentation/latest/address-model.html)

Apache ActiveMQ Artemis has a unique addressing model that is both powerful and flexible and that offers great performance. The addressing model comprises three main concepts: addresses, queues, and routing types.
Apache ActiveMQ Artemis具有一个独特的寻址模型, 该模型既强大又灵活, 并具有出色的性能; 寻址模型包括三个主要概念: addresses, queues, and routing types.

一条消息必须具有要发送到的地址; 当消息到达服务器时, 它将被路由到绑定到该地址任何队列; 路由语义(即, 任播或多播)由地址和队列的"路由类型"确定; 如果队列与任何过滤器绑定, 则仅当过滤器匹配时, 消息才会被路由到该队列; 一个地址可能绑定了许多队列, 甚至没有;

Address

An address represents a messaging endpoint. Within the configuration, a typical address is given a unique name, 0 or more queues, and a routing type.
地址表示消息传递端点; 在配置中, 为一个典型的地址提供一个惟一的名称, 0个或多个队列和一个路由类型;

可以理解为一个地址(目标), 消息可以发送的地址

Queue

A queue is associated with an address. There can be multiple queues per address. Once an incoming message is matched to an address, the message will be sent on to one or more of its queues, depending on the routing type configured. Queues can be configured to be automatically created and deleted.
队列与地址关联; 每个地址可以有多个队列; 一旦传入消息与地址匹配, 该消息将被发送到其一个或多个队列中, 具体取决于配置的路由类型; 可以将队列配置为自动创建和删除;

服务器维护地址和一组队列之间的映射; 零个或多个队列可以绑定到一个地址; 每个队列都可以绑定一个可选的消息过滤器; 路由邮件时, 会将其路由到绑定到邮件地址的队列集; 如果任何队列都与过滤器表达式绑定, 那么该消息将仅路由到与该过滤器表达式匹配的绑定队列的子集;

队列可以是持久的, 这意味着只要队列中的消息是持久的, 队列中包含的消息就可以在服务器崩溃或重新启动后幸免; 即使非持久性队列包含的消息是持久性的(所以重要的是Queue的持久), 它们也无法在服务器重启或崩溃后幸免;

队列也可以是临时的, 这意味着如果在关闭客户端连接之前没有明确删除队列, 则会将它们自动删除;

队列可以与可选的过滤器表达式绑定; 如果提供了过滤器表达式, 则服务器将仅将与该过滤器表达式匹配的消息路由到绑定到该地址的任何队列;

许多队列可以绑定到一个地址; 特定队列最多只能绑定一个地址;

理解为一个消息传输的'通道' (这里称为Queue), 它关联一个或多个Address, 消息传入Queue, 依据配置的路由类型, 分派消息

Routing Types

A routing type determines(确定) how messages are sent to the queues associated with an address. An Apache ActiveMQ Artemis address can be configured with two different routing types.
路由类型确定如何将消息发送到与地址关联的队列; 可以使用两种不同的路由类型来配置Apache ActiveMQ Artemis地址;
分派消息的路由类型

point-to-point 对应的是 Anycast
publish-subscribe 对应的是 Multicast 多播

控制台页面的Diagram的标签图表, 可以非常直观的看出来
绿色的圆点 Broker:"0.0.0.0", 表示一个服务器实例
下面是Order address:"orderer", 表示 address, 或mqtt的主题
下面是Queue queue:"orderer", 表示一个'通道'关联一个或多个Address
下面是Consumer Consumer: 4973610b-e5b9, 表示消费者,或mqtt的关注客户端

更详细的说明文档

参考下面的 简单了解各项配置-> Address 配置

简单了解各项配置

设置JVM参数 (Server JVM settings)

The run scripts set some JVM settings for tuning the garbage collection policy and heap size. We recommend using a parallel garbage collection algorithm to smooth out latency and minimise large GC pauses.

By default Apache ActiveMQ Artemis runs in a maximum of 1GiB of RAM. To increase the memory settings change the -Xms and -Xmx memory settings as you would for any Java program.

If you wish to add any more JVM arguments or tune the existing ones, the run scripts are the place to do it.

bootstrap.xml

用于引导服务器的配置文件, 里面引用了 broker.xml, 使用的是绝对路径! 如果迁移的会有坑..

<broker xmlns="http://activemq.org/schema">

   <jaas-security domain="activemq"/>

   <!-- artemis.URI.instance is parsed from artemis.instance by the CLI startup.
        This is to avoid situations where you could have spaces or special characters on this URI -->
   <server configuration="file:/F:/active-mq-work/mybroker/etc//broker.xml"/>

   <!-- The web server is only bound to localhost by default -->
   <web bind="http://localhost:8161" path="web">
       <app url="activemq-branding" war="activemq-branding.war"/>
       <app url="artemis-plugin" war="artemis-plugin.war"/>
       <app url="console" war="console.war"/>
   </web>


</broker>

broker.xml

This is the main ActiveMQ configuration file. (ActiveMQ Broker 实例的 主要配置文件)

用bootstrap 套一层, 应该是方便不同环境部署吧?

所有的配置参数 参考:

接收器 (Acceptors)

There can be one or more acceptors defined in the acceptors element. There's no upper limit to the number of acceptors per server.

Each acceptor defines a way in which connections can be made to the Apache ActiveMQ Artemis server.

简而言之Acceptors 定义了, ActiveMQ Artemis server 可以接受连接的协议/方式

 <acceptors>
    <!-- Acceptor for every supported protocol -->
    <acceptor name="artemis">tcp://0.0.0.0:61616?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true</acceptor>

    <!-- AMQP Acceptor.  Listens on default AMQP port for AMQP traffic.-->
    <acceptor name="amqp">tcp://0.0.0.0:5672?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=AMQP;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpMinLargeMessageSize=102400;amqpDuplicateDetection=true</acceptor>

    <!-- STOMP Acceptor. -->
    <acceptor name="stomp">tcp://0.0.0.0:61613?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=STOMP;useEpoll=true</acceptor>

    <!-- HornetQ Compatibility Acceptor.  Enables HornetQ Core and STOMP for legacy HornetQ clients. -->
    <acceptor name="hornetq">tcp://0.0.0.0:5445?anycastPrefix=jms.queue.;multicastPrefix=jms.topic.;protocols=HORNETQ,STOMP;useEpoll=true</acceptor>

    <!-- MQTT Acceptor -->
    <acceptor name="mqtt">tcp://0.0.0.0:1883?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=MQTT;useEpoll=true</acceptor>
</acceptors>

SO 它可以当作MQTT服务器来使用的; 在 1883 端口上...

各协议支持情况

Address 配置

<addresses>
    <!-- '地址' 消息发往的目的地; 地址可以是临时地址; -->
    <address name="DLQ">
        <!-- anycast 任意. 多播?-->
        <anycast>
            <!-- 可以多个队列-->
            <queue name="DLQ" />
        </anycast>
    </address>
    <address name="ExpiryQueue">
        <anycast>
            <queue name="ExpiryQueue" />
        </anycast>
    </address>
</addresses>

安全 (Security)

To disable security completely simply set the security-enabled property to false in the broker.xml file.

For performance reasons security is cached and invalidated every so long. To change this period set the property security-invalidation-interval, which is in milliseconds. The default is 10000 ms.
因为性能原因会缓存10000 ms

地址的角色配置 (Role based security for addresses)

Apache ActiveMQ Artemis allows sets of permissions to be defined against the queues based on their address. An exact match on the address can be used or a wildcard(通配符) match can be used.

根据匹配地址的 匹配权限规则

<security-setting match="globalqueues.europe.#">
   <permission type="createDurableQueue" roles="admin"/>
   <permission type="deleteDurableQueue" roles="admin"/>
   <permission type="createNonDurableQueue" roles="admin, guest, europe-users"/>
   <permission type="deleteNonDurableQueue" roles="admin, guest, europe-users"/>
   <permission type="send" roles="admin, europe-users"/>
   <permission type="consume" roles="admin, europe-users"/>
</security-setting>

参考

用户认证 (User credentials)

Apache ActiveMQ Artemis ships with two security manager implementations:

  1. The legacy, deprecated ActiveMQSecurityManager that reads user credentials, i.e. user names, passwords and role information from properties files on the classpath called artemis-users.properties and artemis-roles.properties.
    已弃用的旧版ActiveMQSecurityManager从类路径上名为artemis-users.properties和artemis-roles.properties的属性文件中读取用户凭据, 即用户名, 密码和角色信息;

  2. The flexible, pluggable ActiveMQJAASSecurityManager which supports any standard JAAS login module. Artemis ships with several login modules which will be discussed further down. This is the default security manager.
    灵活的, 可插入的ActiveMQJAASSecurityManager, 它支持任何标准的JAAS登录模块; Artemis附带了几个登录模块, 将在后面进行讨论; 这是默认的安全管理器;

JAAS Security Manager

No matter what login module you're using, you'll need to specify it here in bootstrap.xml.
无论使用什么登录模块, 都需要在bootstrap.xml中进行指定;

bootstrap.xml 指定配置 <jaas-security domain="PropertiesLogin"/>, 其PropertiesLogin 属性是./login.config文件内容 PropertiesLogin的节点

另:
The login.config file is a standard JAAS configuration file.(login.config文件是标准的JAAS配置文件; )

oracle JAAS 配置文件说明

Login Configuration File Structure and Contents

<entry name> { 
    <LoginModule> <flag> <LoginModule options>;
    <LoginModule> <flag> <LoginModule options>;
    // ...
    };

GuestLoginModule

Allows users without credentials (and, depending on how it is configured, possibly also users with invalid credentials) to access the broker
允许没有凭据的用户(并且, 根据配置方式的不同, 可能还有凭据无效的用户)访问broker

简而言之, 就是login.config 里面的 GuestLoginModule 模块允许匿名访问的, 去掉就不允许匿名访问了

activemq {
    //这个模式
   org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule sufficient
       debug=false
       reload=true
       org.apache.activemq.jaas.properties.user="artemis-users.properties"
       org.apache.activemq.jaas.properties.role="artemis-roles.properties";

   org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule sufficient
       debug=false
       org.apache.activemq.jaas.guest.user="admin"
       org.apache.activemq.jaas.guest.role="amq";
};

PropertiesLoginModule

The JAAS properties login module provides a simple store of authentication data, where the relevant user data is stored in a pair of flat files. This is convenient for demonstrations and testing, but for an enterprise system, the integration with LDAP is preferable.

核心API 与JMS

artemis API文档
broker 的api

ServerLocator

Clients use ServerLocator instances to create ClientSessionFactory instances.

ServerLocator instances are used to locate servers and create connections to them.(ServerLocator 用于定位服务器并创建与服务器的连接; )

in JMS terms think of a ServerLocator in the same way you would a JMS Connection Factory.

ServerLocator instances are created using the ActiveMQClient factory class.

类似与JMS的ConnectionFactory

ClientSessionFactory

Clients use ClientSessionFactory instances to create ClientSession instances. ClientSessionFactory instances are basically the connection to a server

In JMS terms think of them as JMS Connections.

ClientSessionFactory instances are created using the ServerLocator class.

类似与JMS的Connections

ClientSession

A client uses a ClientSession for consuming and producing messages and for grouping them in transactions. (客户端使用ClientSession来消费和产生消息并将它们分组到事务中)

ClientSession instances group ClientConsumer instances and ClientProducer instances.
Destination

使用它来创建 Queue(类似与JMS的 Destination, 一个'通道')
然后再使用 session 创建生产者或消费者, session.createProducer(dest);

还有点不一样的是, 可以注册一个成功发送后的异步回调, 以完全保证已发送的消息已到达服务器.

使用JMS (Using JMS)

(http://activemq.apache.org/components/artemis/documentation/latest/using-jms.html)

依赖
妈蛋这些依赖, 文档一句不提, 只能在 git 搜代码! 示例也是私有maven 仓库

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

<!--<dependency>-->
    <!--<groupId>org.apache.activemq</groupId>-->
    <!--<artifactId>artemis-jms-client-all</artifactId>-->
<!--</dependency>-->
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>artemis-server</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>artemis-jms-server</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>artemis-jms-client</artifactId>
</dependency>

<!-- 另外 JMX 依赖这个-->
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-broker</artifactId>
</dependency>

JNDI JMS

System.setProperty("java.naming.factory.initial", "org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory");
System.setProperty("connectionFactory.invmConnectionFactory", "tcp://127.0.0.1:61616");

Context ctx = new InitialContext();

//Now we'll look up the connection factory from which we can create
//connections to myhost:5445:

ConnectionFactory cf = (ConnectionFactory)ctx.lookup("ConnectionFactory");

错误
java.lang.ClassNotFoundException 异常...

直接实例JMS资源 Directly (instantiating JMS Resources without using JNDI)

/**
注意版本: 这是 activemq 版的代码
org.apache.activemq.ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin","jk123.com","tcp://localhost:61616");
//ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin","jk123.com",ActiveMQConnection.DEFAULT_BROKER_URL);
//System.out.println("connect to: "+ActiveMQConnection.DEFAULT_BROKER_URL);//connect to: failover://tcp://localhost:61616
// 使用的协议是 `OpenWire`; 即接收器的名称是: `<acceptor name="artemis" ...` 的

*/

/**artemis 版**/
Map<String, Object> param = new HashMap<>();
param.put("host", "192.168.33.139");
param.put("port", "61616");
// 第一个参数连接协议实现? CORE  , 第二个手动调出来, 文档也没有..
TransportConfiguration transportConfiguration = new TransportConfiguration(NettyConnectorFactory.class.getName(), param);
/*
 第一个参数连接类型 
 CF (default)	javax.jms.ConnectionFactory 
 XA_CF	javax.jms.XAConnectionFactory
 QUEUE_CF	javax.jms.QueueConnectionFactory
 TOPIC_CF	javax.jms.TopicConnectionFactory
 ...
 参考文档 Configuration for Connection Factory Types
**/
ConnectionFactory cf = org.apache.activemq.artemis.api.jms.ActiveMQJMSClient.ActiveMQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF,transportConfiguration);
	

//Next we create a JMS connection using the connection factory:
Connection connection = connectionFactory.createConnection();

//And we create a non transacted JMS Session, with AUTO\_ACKNOWLEDGE
//acknowledge mode:

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

Queue orderQueue = session.createQueue("orderer");
// Queue orderQueue = (Queue)ctx.lookup("DLQ");

//We create a MessageProducer that will send orders to the queue:
MessageProducer producer = session.createProducer(orderQueue);

//And we create a MessageConsumer which will consume orders from the
//queue:
MessageConsumer consumer = session.createConsumer(orderQueue);

//We make sure we start the connection, or delivery won't occur on it:
connection.start();

//We create a simple TextMessage and send it:
TextMessage message = session.createTextMessage("This is an order 1000001");
producer.send(message);

//And we consume the message:
TextMessage receivedMessage = (TextMessage)consumer.receive();
System.out.println("Got order: " + receivedMessage.getText());

connection.close();

用户认证

持久化 (Persistence)

Apache ActiveMQ Artemis ships with two persistence options.(Apache ActiveMQ Artemis 提供两种持久化选项)

The file journal which is highly optimized for the messaging use case and gives great performance(高度优化的文件日志), and also the JDBC Store(和JDBC存储), which uses JDBC to connect to a database of your choice. The JDBC Store is still under development, but it is possible to use its journal features, (essentially everything except for paging and large messages).

在生产者 发送消息的时候可以通过api 设置 producer.setDeliveryMode(DeliveryMode.PERSISTENT)

TODO 具体配置

File Journal (Default)

The file journal is an append only journal. It consists of a set of files on disk. Each file is pre-created to a fixed size and initially filled with padding. As operations are performed on the server, e.g. add message, update message, delete message, records are appended to the journal. When one journal file is full we move to the next one.

Bindings journal 和 Message journal

  • Bindings journal.

This journal is used to store bindings related data. That includes the set of queues that are deployed on the server and their attributes. It also stores data such as id sequence counters.

The bindings journal is always a NIO journal as it is typically low throughput compared to the message journal.

The files on this journal are prefixed as activemq-bindings. Each file has a bindings extension. File size is 1048576, and it is located at the bindings folder.

  • Message journal.

This journal instance stores all message related data, including the message themselves and also duplicate-id caches.

By default Apache ActiveMQ Artemis will try and use an AIO journal. If AIO is not available, e.g. the platform is not Linux with the correct kernel version or AIO has not been installed then it will automatically fall back to using Java NIO which is available on any Java platform.

The files on this journal are prefixed as activemq-data. Each file has an amq extension. File size is by the default 10485760 (configurable), and it is located at the journal folder.

Configuring the bindings journal

The bindings journal is configured using the following attributes in broker.xml

bindings-directory

This is the directory in which the bindings journal lives. The default value is data/bindings.

create-bindings-dir

If this is set to true then the bindings directory will be automatically created at the location specified in bindings-directory if it does not already exist. The default value is true

Configuring the message journal

The message journal is configured using the following attributes in broker.xml

... 看文档

JDBC Persistence

WARNING: The Apache ActiveMQ Artemis JDBC persistence store is under development and is included for evaluation purposes.

e....

服务器集群

官页文档 Clusters

接收器 Configuring the Transport

One of the most important concepts in Apache ActiveMQ Artemis transports is the acceptor. Let's dive straight in and take a look at an acceptor defined in xml in the configuration file broker.xml.
Apache ActiveMQ Artemis传输中最重要的概念之一是接受器; 让我们直接看一下配置文件broker.xml中xml中定义的接受器;

<acceptor name="netty">tcp://localhost:61617</acceptor>

接受器始终在acceptors元素内定义; acceptors元素中可以定义一个或多个受体; 每个服务器的接收器数量没有上限;

连接器 Connectors

Whereas acceptors are used on the server to define how we accept connections, connectors are used to define how to connect to a server.
服务器上使用接受器来定义我们如何接受连接, 而连接器用于定义如何连接到服务器;

Let's look at a connector defined in our broker.xml file:
<connector name="netty">tcp://localhost:61617</connector>

可以在连接器元素内定义连接器; 连接器元素中可以定义一个或多个连接器; 每个服务器的连接器数量没有上限;

Out of the box, Apache ActiveMQ Artemis currently uses Netty, a high performance low level network library.
开箱即用的Apache ActiveMQ Artemis当前使用Netty, 一个高性能的底层网络库;

当配置核心 ClientSessionFactory 直接与服务器通信时, 也间接使用连接器; 尽管在这种情况下, 无需在服务器端配置中定义此类连接器, 但我们只需要指定适当的URI;

下面是一个创建ClientSessionFactory的示例, 它将直接连接到我们在本章前面定义的接受器, 它使用标准的Netty TCP传输, 并尝试在端口61617上连接到本地主机(默认)

ServerLocator locator = ActiveMQClient.createServerLocator("tcp://localhost:61617");

ClientSessionFactory sessionFactory = locator.createClientSessionFactory();

ClientSession session = sessionFactory.createSession(...);

in short API 是使用服务器定义的 连接器 来进行交互的, 集群中节点通讯,也是使用连接器

Apache ActiveMQ Artemissupports using a single port for all protocols, Apache ActiveMQ Artemis willautomatically detect which protocol is being used CORE, AMQP, STOMP, MQTT or OPENWIRE and use the appropriate Apache ActiveMQ Artemis handler.

单端口 多协议
<acceptor name="netty">tcp://localhost:61617?protocols=CORE,AMQP</acceptor>

Apache ActiveMQ Artemis集群允许将Apache ActiveMQ Artemis服务器组分组在一起, 以共享消息处理负载; 集群中的每个活动节点都是活动的Apache ActiveMQ Artemis服务器, 该服务器管理自己的消息并处理自己的连接

集群由每个节点组成, 这些节点在核心配置文件broker.xml中声明与其他节点的群集连接; 当一个节点与另一个节点形成集群连接时, 在内部会在其与另一个节点之间创建一个Core Bridges, 这是在后台透明进行的-您不必声明显式桥 对于每个节点; 这些群集连接允许消息在群集的节点之间流动以平衡负载;

桥接器 Core Bridges

Core Bridges

bridges (桥接器)的功能是使用来自源队列的消息, 并将它们转发到目标地址, 通常在其他Apache ActiveMQ Artemis服务器上;
源服务器和目标服务器不必位于同一群集中, 这使得桥接适合于将消息从一个群集可靠地发送到另一个群集

Configuring Bridges

Bridges are configured in broker.xml. Let's kick off with an example (this is actually from the bridge example):

broker.xml

<bridge name="my-bridge">
   <queue-name>sausage-factory</queue-name>
   <forwarding-address>mincing-machine</forwarding-address>
   <filter string="name='aardvark'"/>
   <transformer-class-name>
      org.apache.activemq.artemis.jms.example.HatColourChangeTransformer
   </transformer-class-name>
   <retry-interval>1000</retry-interval>
   <ha>true</ha>
   <retry-interval-multiplier>1.0</retry-interval-multiplier>
   <initial-connect-attempts>-1</initial-connect-attempts>
   <reconnect-attempts>-1</reconnect-attempts>
   <failover-on-server-shutdown>false</failover-on-server-shutdown>
   <use-duplicate-detection>true</use-duplicate-detection>
   <confirmation-window-size>10000000</confirmation-window-size>
   <user>foouser</user>
   <password>foopassword</password>
   <routing-type>PASS</routing-type>
   <static-connectors>
      <connector-ref>remote-connector</connector-ref>
   </static-connectors>
   <!-- alternative to static-connectors
   <discovery-group-ref discovery-group-name="bridge-discovery-group"/>
   -->
</bridge>


Let's take a look at all the parameters in turn(依次):
依次查看所有参数

name attribute. All bridges must have a unique name in the server.

queue-name. This is the unique name of the local queue that the bridge consumes from, it's a mandatory parameter.(这是网桥使用的本地队列的唯一名称, 它是必填参数; )
The queue must already exist by the time the bridge is instantiated at start-up.(队列在启动时实例化时, 该队列必须已经存在; )

forwarding-address. This is the address on the target server that the message will be forwarded to. If a forwarding address is not specified, then the original address of the message will be retained.

filter-string. An optional filter string can be supplied. If specified then only messages which match the filter expression specified in the filter string will be forwarded. The filter string follows the ActiveMQ Artemis filter expression syntax described in Filter Expressions.

transformer-class-name. An optional transformer can be specified. This gives you the opportunity to transform the message's header or body before forwarding it. See the transformer chapter for more details about transformer-specific configuration.

ha. This optional parameter determines whether or not this bridge should support high availability. True means it will connect to any available server in a cluster and support failover. The default value is false.

....略

动态发现 Dynamic Discovery

Server discovery uses UDP multicast or JGroups to broadcast server connection settings.

用UDP, 配置还巨麻烦 .. 略

静态发现 Discovery using static Connectors

Discovery using static Connectors

配置说明

Configuring Cluster Connections
Cluster connections group servers into clusters so that messages can be load balanced between the nodes of the cluster. Let's take a look at a typical cluster connection. Cluster connections are always defined in broker.xml inside a cluster-connection element. There can be zero or more cluster connections defined per Apache ActiveMQ Artemis server.

<cluster-connections>
   <cluster-connection name="my-cluster">
      <address></address>
      <connector-ref>netty-connector</connector-ref>
      <check-period>1000</check-period>
      <connection-ttl>5000</connection-ttl>
      <min-large-message-size>50000</min-large-message-size>
      <call-timeout>5000</call-timeout>
      <retry-interval>500</retry-interval>
      <retry-interval-multiplier>1.0</retry-interval-multiplier>
      <max-retry-interval>5000</max-retry-interval>
      <initial-connect-attempts>-1</initial-connect-attempts>
      <reconnect-attempts>-1</reconnect-attempts>
      <use-duplicate-detection>true</use-duplicate-detection>
      <message-load-balancing>ON_DEMAND</message-load-balancing>
      <max-hops>1</max-hops>
      <confirmation-window-size>32000</confirmation-window-size>
      <call-failover-timeout>30000</call-failover-timeout>
      <notification-interval>1000</notification-interval>
      <notification-attempts>2</notification-attempts>
      <discovery-group-ref discovery-group-name="my-discovery-group"/>
   </cluster-connection>
</cluster-connections>

address 使用集群的 address名称,支持一些匹配规则, 空的话是所有

connector-ref 连接器ref, 指向自己节点的连接器? 参考上 [连接器]

check-period 用于检查集群连接是否未能从另一台服务器接收ping的时间段(以毫秒为单位); 默认值为30000

min-large-message-size 如果消息大小(以字节为单位)大于此值, 则在通过网络发送给其他群集成员时, 它将被分为多个段; 默认值为102400;

call-timeout 默认值为30000 毫秒, 消息确认超时时间(节点间)

retry-interval 桥接器的重连接间隔时间 默认值为500毫秒;

retry-interval-multiplier 桥接器的重连接间隔时间的指数避让

max-retry-interval. The maximum delay (in milliseconds) for retries. Default is 2000.

initial-connect-attempts 系统最初尝试连接集群中节点的次数; 如果达到最大重试次数, 则该节点将被视为永久关闭, 并且系统不会将消息路由到该节点; 默认值为-1(无限次重试);

message-load-balancing. 此参数确定是否/如何在群集的其他节点之间分发消息; 它可以是三个值之一-OFF, STRICT或ON_DEMAND(默认值);
如果将其设置为OFF, 则消息将永远不会转发到群集中的另一个节点
如果将其设置为ON_DEMAND, 则Apache ActiveMQ Artemis仅将消息转发到集群的其他节点, 前提是转发消息的地址具有包含使用方的队列
如果将其设置为STRICT, 则即使群集其他节点上的相同队列可能根本没有使用方(消费者), 或者它们的使用方具有不匹配的消息过滤器(选择器), 每个传入消息也将是循环的; 请注意, 即使其他参数上没有相同名称的队列, 即使此参数设置为STRICT, Apache ActiveMQ Artemis也不会将消息转发到其他节点; 实测貌似无效, 没用消费者的节点是

static-connectors 元素属性, 其他节点的连接器的 ref

   <static-connectors>
        <connector-ref>server180-connector</connector-ref>
    </static-connectors>
</cluster-connection>

项目实例

参考 Clustered Static Discovery 示例项目

This example demonstrates how to configure a cluster using a list of connectors rather than UDP for discovery

./artemis.cmd create --allow-anonymous --silent --force --no-web --user guest --password guest --role guest --port-offset 0 --data ./data --allow-anonymous --no-autotune --verbose /target/server0
./artemis.cmd create --allow-anonymous --silent --force --no-web --user guest --password guest --role guest --port-offset 0 --data ./data --allow-anonymous --no-autotune --verbose /target/server1

./artemis.cmd create --allow-anonymous --silent --force --no-web --user admin --password jk123.com --role admin --port-offset 0 --data ./data --allow-anonymous --no-autotune --verbose /target/server1

server0 完整 配置broker.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
   <core xmlns="urn:activemq:core">
      <bindings-directory>./data/bindings</bindings-directory>
      <journal-directory>./data/journal</journal-directory>
      <large-messages-directory>./data/largemessages</large-messages-directory>
      <paging-directory>./data/paging</paging-directory>
      <!-- Connectors -->
      <connectors>
         <connector name="netty-connector">tcp://localhost:61616</connector>
         <!-- connector to the server1 -->
         <connector name="server1-connector">tcp://localhost:61617</connector>
      </connectors>
      <!-- Acceptors -->
      <acceptors>
         <acceptor name="netty-acceptor">tcp://localhost:61616</acceptor>
		 <acceptor name="mqtt">tcp://0.0.0.0:1883?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=MQTT;useEpoll=true</acceptor>
      </acceptors>

      <cluster-connections>
         <cluster-connection name="my-cluster">
            <connector-ref>netty-connector</connector-ref>
            <retry-interval>500</retry-interval>
            <use-duplicate-detection>true</use-duplicate-detection>
            <message-load-balancing>STRICT</message-load-balancing>
            <max-hops>1</max-hops>
            <static-connectors>
               <connector-ref>server1-connector</connector-ref>
            </static-connectors>
         </cluster-connection>
      </cluster-connections>

      <!-- Other config -->
      <security-settings>
         <!--security for example queue-->
         <security-setting match="exampleQueue">
            <permission roles="guest" type="createDurableQueue"/>
            <permission roles="guest" type="deleteDurableQueue"/>
            <permission roles="guest" type="createNonDurableQueue"/>
            <permission roles="guest" type="deleteNonDurableQueue"/>
            <permission roles="guest" type="consume"/>
            <permission roles="guest" type="send"/>
         </security-setting>
      </security-settings>

      <addresses>
         <address name="exampleQueue">
            <anycast>
               <queue name="exampleQueue"/>
            </anycast>
         </address>
      </addresses>
   </core>
</configuration>

server1 完整 配置broker.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
   <core xmlns="urn:activemq:core">
      <bindings-directory>./data/bindings</bindings-directory>
      <journal-directory>./data/journal</journal-directory>
      <large-messages-directory>./data/largemessages</large-messages-directory>
      <paging-directory>./data/paging</paging-directory>
      <!-- Connectors -->
      <connectors>
         <connector name="netty-connector">tcp://localhost:61617</connector>
         <!-- connector to the server0 -->
         <connector name="server0-connector">tcp://localhost:61616</connector>
      </connectors>
      <!-- Acceptors -->
      <acceptors>
         <acceptor name="netty-acceptor">tcp://localhost:61617</acceptor>
      </acceptors>
      <!-- Clustering configuration -->
      <cluster-connections>
         <cluster-connection name="my-cluster">
            <connector-ref>netty-connector</connector-ref>
            <retry-interval>500</retry-interval>
            <use-duplicate-detection>true</use-duplicate-detection>
            <message-load-balancing>STRICT</message-load-balancing>
            <max-hops>1</max-hops>
            <static-connectors>
               <connector-ref>server0-connector</connector-ref>
            </static-connectors>
         </cluster-connection>
      </cluster-connections>
      <!-- Other config -->
      <security-settings>
         <!--security for example queue-->
         <security-setting match="exampleQueue">
            <permission roles="guest" type="createDurableQueue"/>
            <permission roles="guest" type="deleteDurableQueue"/>
            <permission roles="guest" type="createNonDurableQueue"/>
            <permission roles="guest" type="deleteNonDurableQueue"/>
            <permission roles="guest" type="consume"/>
            <permission roles="guest" type="send"/>
         </security-setting>
      </security-settings>
      <addresses>
         <address name="exampleQueue">
            <anycast>
               <queue name="exampleQueue"/>
            </anycast>
         </address>
      </addresses>
   </core>
</configuration>

集群用户认证 Cluster User Credentials

When creating connections between nodes of a cluster to form a cluster connection, Apache ActiveMQ Artemis uses a cluster user and cluster password which is defined in broker.xml:

<cluster-user>ACTIVEMQ.CLUSTER.ADMIN.USER</cluster-user>
<cluster-password>CHANGE ME!!</cluster-password>

集群的各种结构差异 Cluster topologies

上面示例即是 对称拓扑结构
http://activemq.apache.org/components/artemis/documentation/latest/clusters.html

踩坑

  • MQTT协议客户端, 无限连接失败 WARN [org.apache.activemq.artemis.core.server] AMQ222061: Client connection failed, clearing up resources for session 0d071181-ec05-11ea-90a3-005056c00008

配置有错误

服务端消息负载均衡 Server-Side Message Load Balancing

假设有 A, B, C, D 四个节点服务器,我们在集群的每个节点上都有一个名为OrderQueue的队列,
同样有 Ca, Cb, Cc, Cd 四个消费者连接到对应节点服务器上.

有一个客户端Pa连接到A节点服务器上, 往OrderQueue发送消息,

  • 如果在节点A上未定义集群连接, 那么当OrderQueue消息到达节点A时, 它们将全部被Ca消费者接受.
  • 如果我们在节点A上定义集群连接, 那么当OrderQueue消息到达节点A 它们将以循环方式分布在集群的所有节点之间;
    到达节点A的消息可能按以下顺序在节点之间分配: B, D, C, A, B, D, C, A, B, D; 确切的顺序取决于节点启动的顺序 , 但使用的算法是循环轮询;

换句话说, 对于客户端是透明的只需连接其中一个节点即可, 当然客户端可以使用静态发现可选择的连接节点

Configuring client discovery
A list of servers to be used for the initial connection attempt can be specified in the connection URI using a syntax with (), e.g.:

(tcp://myhost:61616,tcp://myhost2:61616)?reconnectAttempts=5

客户端负载均衡 Client-Side Load balancing

TODO

指标采集

有四种方式可以访问/管理artemis 可管理功能都是一样的

There are four ways to access Apache ActiveMQ Artemis management API:

  1. Using JMX -- JMX is the standard way to manage Java applications

  2. Using Jolokia -- Jolokia exposes the JMX API of an application through a REST interface

  3. Using the Core Client -- management operations are sent to Apache ActiveMQ Artemis server using Core Client messages

  4. Using any JMS Client -- management operations are sent to Apache ActiveMQ Artemis server using JMS Client messages

JMX 获取

参考文档 Management 章节

ActiveMQ使用过程中, 可以使用自带的控制台进行相关的操作以及查看, 但是当队列数相当多的时候, 在查询以及整体的监控上, 就可能相当的不便; 所以可通过JMX的方式, 进行MQ中队列相关指标的以及整体健康性能等收集展示;

关于JMX

JMX(Java Management Extensions, 即Java管理扩展)是一个为应用程序, 设备, 系统等植入管理功能的框架; JMX可以跨越一系列异构操作系统平台, 系统体系结构和网络传输协议, 灵活的开发无缝集成的系统, 网络和服务管理应用;

使用了 Java Bean 模式来传递信息; 一般说来, JMX 使用有名的 MBean,其内部包含了数据信息, 这些信息可能是: 应用程序配置信息, 模块信息, 系统信息, 统计信息等; 另外, MBean 也可以设立可读写的属性, 直接操作某些函数甚至启动 MBean 可发送的 notification 等;

JMX 规范可以分为三层: 设备层, 代理层, 分布式服务层; 设备层规范定义了编写可由 JMX 管理的资源的标准, 即如何写 MBean; 代理曾规范定义了创建代理的规范, 封装了 MBean Server; 分布式服务层主要定义了对代理层进行操作的管理接口和构件;

JMXServiceURL格式说明
service:jmx:rmi://localhost:0/jndi/rmi://localhost:1099/jmxrmi
部分可以省略掉

service:jmx: 这个是JMX URL的标准前缀, 所有的JMX URL都必须以该字符串开头, 否则会抛MalformedURLException
rmi: 这个是jmx connector server的传输协议, 在这个url中是使用rmi来进行传输的
localhost:0 这个是jmx connector server的IP和端口, 也就是真正提供服务的host和端口, 可以忽略, 那么会在运行期间随意绑定一个端口提供服务
jndi/rmi://localhost:1099/jmxrmi 这个是 jmx connector server 的路径, 具体含义取决于前面的传输协议; 比如该URL中这串字符串就代表着该jmx connector server的stub是使用 jndi api 绑定在 rmi://localhost:1099/jmxrmi 这个地址

Configuring JMX

By default, JMX is enabled to manage Apache ActiveMQ Artemis.
It can be disabled by setting jmx-management-enabled to false in broker.xml:
默认是启用的true

<!-- false to disable JMX management for Apache ActiveMQ Artemis -->
<jmx-management-enabled>true</jmx-management-enabled>


Configuring remote JMX Access

主要配置文件是: management.xml

By default remote JMX access to Artemis is disabled for security reasons.
出于安全原因, 默认情况下, 禁用对Artemis的远程JMX访问; (JDK自带的jmx? 即Dcom.sun.management.jmxremote.port等参数指定的?)

-Dcom.sun.management.jmxremote.port=1099
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

Artemis has a JMX agent which allows access to JMX mBeans remotely. This is configured via the connector element in the management.xml configuration file. To enable this you simply add the following xml:
Artemis有一个JMX代理,该代理允许远程访问JMX mBean; 这是通过management.xml配置文件中的connector元素配置的; 要启用此功能, 您只需添加以下xml:

<connector connector-port="1099"/>

This exposes the agent remotely on the port 1099. If you were connecting via jconsole you would connect as a remote process using the service url service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi and an appropriate user name and password.

Role Based Authorisation for JMX

代码

Client Kickoff
The client-kickoff example shows how to terminate client connections given an IP address using the JMX management API.

参考 distribution 目录 .\examples\features\standard\client-kickoff 的示例代码

依赖

<dependency>
	<groupId>org.apache.activemq</groupId>
	<artifactId>activemq-broker</artifactId>
</dependency>

获取 Broker 的代理控制

import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl;
import org.apache.activemq.artemis.api.core.management.ObjectNameBuilder;
import org.apache.activemq.broker.jmx.BrokerViewMBean;
import org.apache.activemq.broker.jmx.QueueViewMBean;
import javax.jms.QueueConnection;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.naming.InitialContext;

    QueueConnection connection = null;
    InitialContext initialContext = null;
    try {
        /********** 这部分是 jms的
        // Step 1. Create an initial context to perform the JNDI lookup.
        initialContext = new InitialContext();

        // Step 2. Perform a lookup on the Connection Factory
        QueueConnectionFactory cf = (QueueConnectionFactory) initialContext.lookup("ConnectionFactory");

        // Step 3.Create a JMS Connection
        connection = cf.createQueueConnection();

        // Step 4. Set an exception listener on the connection to be notified after a problem occurred
        final AtomicReference<JMSException> exception = new AtomicReference<>();
        connection.setExceptionListener(new ExceptionListener() {
        @Override
        public void onException(final JMSException e) {
            exception.set(e);
        }
        });
                // Step 5. We start the connection
        connection.start();
       ***********/
        // Step 6. Create an ActiveMQServerControlMBean proxy to manage the server
        String brokerName = "0.0.0.0";  // configured e.g. in broker.xml <broker-name> element
        //而这个Domain 是 <jmx-domain>my.org.apache.activemq</jmx-domain> 定义的, 默认是org.apache.activemq.artemis, 实在找不到 还可以通过jconsole看到
        ObjectNameBuilder  objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), brokerName, true);
        ObjectName  brokerObjectName = objectNameBuilder.getActiveMQServerObjectName();
        //ObjectName brokerObjectName = ObjectNameBuilder.DEFAULT.getActiveMQServerObjectName();
        
        JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(JMX_URL), new HashMap<String, String>());
        MBeanServerConnection mbsc = connector.getMBeanServerConnection();
        ActiveMQServerControl serverControl = MBeanServerInvocationHandler.newProxyInstance(mbsc, brokerObjectName, ActiveMQServerControl.class, false);
        /**
        这个 ActiveMQServerControl 便是jmx 控制broker 的代理对;
        可以获取所有 Queue, 创建 address等
            System.out.println(serverControl.getQueueNames() );
            serverControl.createAddress(name, routingTypes)
       **/
        // Step 7. List the remote address connected to the server
        System.out.println("List of remote addresses connected to the server:");
        System.out.println("----------------------------------");
        //获取到已连接到服务器的客户端; 注意上面注释了啊
        String[] remoteAddresses = serverControl.listRemoteAddresses();
        for (String remoteAddress: remoteAddresses) {
        System.out.println(remoteAddress);
        }
        System.out.println("----------------------------------");

        // Step 8. Close the connections for the 1st remote address and kickoff the client
        serverControl.closeConnectionsForAddress(remoteAddresses[0]);

        // Sleep a little bit so that the stack trace from the server won't be
        // mingled with the JMSException received on the ExceptionListener
        Thread.sleep(1000);

        // Step 9. Display the exception received by the connection's ExceptionListener
        System.err.println("\nException received from the server:");
        System.err.println("----------------------------------");
//	         exception.get().printStackTrace();
        System.err.println("----------------------------------");
    } finally {
        // Step 10. Be sure to close the resources!
        if (initialContext != null) {
        initialContext.close();
        }
        if (connection != null) {
        connection.close();
        }
    }



获取 Queue 的代理控制

//前面代码
String brokerName = "0.0.0.0";  
//注意名称在broker.xm定义的 configured e.g. in broker.xml <broker-name> element, 实际是core的子元素 <name>0.0.0.0</name> 日
//而这个Domain 是 <jmx-domain>my.org.apache.activemq</jmx-domain> 定义的, 默认是org.apache.activemq.artemis, 实在找不到 还可以通过jconsole看到
ObjectNameBuilder  objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), brokerName, true);
//封装方法通过 ObjectNameBuilder 获得 QueueObjectName 
ObjectName queueObjectName = objectNameBuilder.getQueueObjectName(SimpleString.toSimpleString("orderer"), SimpleString.toSimpleString("orderer"), RoutingType.ANYCAST);
QueueControl queueControl = (QueueControl) MBeanServerInvocationHandler.newProxyInstance(conn, queueObjectName, QueueControl.class, true);
System.out.println(queueControl);
System.out.println(queueControl.getConsumerCount());
System.out.println(queueControl.getName());


///类似的有等等控制

//ObjectName addressObjectName =  objectNameBuilder.getAddressObjectName(SimpleString.toSimpleString(addressName) );
//AddressControl addressControl = (AddressControl) MBeanServerInvocationHandler.newProxyInstance(conn, addressObjectName, AddressControl.class, true);

获取 MBeanInfo

artemis 获取指标, 全部统一通过对应的对象名(ObjectNam ) 获取到JMX的javax.management.MBeanInfo.MBeanInfo 来描述各项指标 (元描述)
只有元描述必须通过反射方法来调用对应的方法.. so 它既包含控制,又包含指标

//遍历 Broker -> Address  -> Queue 的指标信息
final String  JMX_URL = "service:jmx:rmi:///jndi/rmi://127.0.0.1:1099/jmxrmi";

String brokerName = "0.0.0.0";  
Map<String, Object> credentials = new HashMap<>();
//credentials.put(JMXConnector.CREDENTIALS, new String[] {"admin", "jk123.com"});
JMXServiceURL urls = new JMXServiceURL(JMX_URL);
JMXConnector connector = JMXConnectorFactory.connect(urls,credentials);
connector.connect();
MBeanServerConnection conn = connector.getMBeanServerConnection();

ObjectNameBuilder  objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), brokerName, true);
ObjectName  brokerObjectName = objectNameBuilder.getActiveMQServerObjectName();
ActiveMQServerControl serverControl = MBeanServerInvocationHandler.newProxyInstance(conn, brokerObjectName, ActiveMQServerControl.class, true);
// MBeanInfo brokerInfo =  (MBeanInfo) conn.getMBeanInfo(brokerObjectName);
// System.out.println(brokerInfo );
// 实例对象 ActiveMQServerControl
String[] addressNames = serverControl.getAddressNames();
for (String addressName: addressNames) {
    ObjectName addressObjectName =  objectNameBuilder.getAddressObjectName(SimpleString.toSimpleString(addressName) );
    AddressControl addressControl = (AddressControl) MBeanServerInvocationHandler.newProxyInstance(conn, addressObjectName, AddressControl.class, true);
    String [] queueNames =  addressControl.getQueueNames();
    for (String queueName: queueNames) {
        ObjectName queueObjectName = objectNameBuilder.getQueueObjectName(SimpleString.toSimpleString(addressName), SimpleString.toSimpleString(queueName), RoutingType.ANYCAST);
        //判断下是否存在的
        if(conn.isRegistered(queueObjectName)  ) {
            MBeanInfo queueInfo = conn.getMBeanInfo(queueObjectName);
            System.out.println(queueInfo );
            //QueueControl queueControl = (QueueControl) MBeanServerInvocationHandler.newProxyInstance(conn, addressObjectName, QueueControl.class, true);
            // 实例对象 queueControl
        }
    }
}
connector.close();

参考下文档 Metrics chapter

踩坑

  1. 获取jmx代理控制, 总是报 java.rmi.ConnectException Connection refused to host: xxx.xxx.xxx.xxx ; 异常

management.xmlconnector connector-host属性需要指向具体的实例名称(在broker.xml定义的)

<!-- configured e.g. in broker.xml <broker-name> element-->
<connector connector-port="1099" connector-host="0.0.0.0"/>
  1. 网页版 console 报错 Origin http://192.168.33.163:8161 is not allowed to call this agent

jolokia-access.xml 配置跨域

 <!-- Allow cross origin access from localhost ... -->
 <allow-origin>*://localhost*</allow-origin>

这个配置文件参考 Chapter 4. Security

  1. 无法以'远程形式的访问' Connection timed out: connect
    恶心的一批, 奇怪的错误提示

修改配置 management.xml

<connector connector-port="1099" rmi-registry-port="1199" connector-host="192.168.33.163" />

rmi-registry-port

The port that the RMI registry binds to. If not set, the port is always random. Set to avoid problems with remote JMX connections tunnelled through firewall.
RMI注册表绑定到的端口; 如果未设置, 则端口始终是随机的; 设置为避免通过防火墙隧穿的远程JMX连接出现问题;

connector-host
指向自己的ip
或者brokerName?

真的就是防火墙的问题!!!
参考文档 Configuring remote JMX Access

JMX 需要两个端口通讯, 防火墙需要允许 10991199 端口;

最后jmx url service:jmx:rmi://192.168.33.180:1199/jndi/rmi://192.168.33.163:1099/jmxrmi

JDK的 jconsole.exeGUI工具, 可以管理查看jmx接口

Core Client 获取

Using Management Message API

The management message API in ActiveMQ Artemis is accessed by sending Core Client messages to a special address, the management address.
通过将Core Client消息发送到特殊地址(管理地址)来访问ActiveMQ Artemis中的管理消息API;

These steps can be simplified to make it easier to invoke management operations using Core messages:

  1. Create a ClientRequestor to send messages to the management address and receive replies

  2. Create a ClientMessage

  3. Use the helper class org.apache.activemq.artemis.api.core.management.ManagementHelper to fill the message with the management properties

  4. Send the message using the ClientRequestor

  5. Use the helper class org.apache.activemq.artemis.api.core.management.ManagementHelper to retrieve the operation result from the management reply.

代码

//		String brokerName = "0.0.0.0";
//		ObjectNameBuilder objectNameBuilder = ObjectNameBuilder.create(ActiveMQDefaultConfiguration.getDefaultJmxDomain(), brokerName, true);
//		ObjectName brokerObjectName = objectNameBuilder.getActiveMQServerObjectName();

Map<String, Object> param = new HashMap<>();
//param.put("host", "127.0.0.1");
param.put("host", "192.168.33.163");
param.put("port", "61616");
//用的是CORE 协议
TransportConfiguration transportConfiguration = new TransportConfiguration(NettyConnectorFactory.class.getName(), param);

ServerLocator locator = ActiveMQClient.createServerLocatorWithoutHA(transportConfiguration);
ClientSessionFactoryImpl factory = (ClientSessionFactoryImpl) locator.createSessionFactory();
ClientSession session = factory.createSession();

ClientRequestor requestor = new ClientRequestor(session, "activemq.management");
ClientMessage message = session.createMessage(false);
ManagementHelper.putAttribute(message, "queue.exampleQueue", "messageCount");

session.start();

ClientMessage reply = requestor.request(message);
Long count =  (Long) ManagementHelper.getResult(reply);
System.out.println("There are " + count + " messages in exampleQueue");


/// 管理是使用 JMSManagementHelper.putOperationInvocation 相关put执行操作 
/// 操作属性参考 Notification Types and Headers
// Step 17. Use a helper class to fill the JMS message with management information:
// * the object name of the resource to manage (i.e. the queue)
// * in this case, we want to call the "removeMessage" operation with the JMS MessageID
// of the message sent to the queue in step 8.
JMSManagementHelper.putOperationInvocation(m, ResourceNames.QUEUE + "exampleQueue", "removeMessages", FilterConstants.ACTIVEMQ_USERID + " = '" + message.getJMSMessageID() + "'");

// Step 18 Use the requestor to send the request and wait for the reply
reply = requestor.request(m);

// Step 19. Use a helper class to check that the operation has succeeded
boolean success = JMSManagementHelper.hasOperationSucceeded(reply);
System.out.println("operation invocation has succeeded: " + success);

Spring Boot 嵌入式 (Artemis Support)

<!-- 
    注意版本!
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-artemis</artifactId>
</dependency>

Spring Boot 文档 boot-features-artemis

Spring Boot can auto-configure a ConnectionFactory when it detects that Artemis is available on the classpath.
当Spring Boot检测到Artemis在类路径上可用时, 它可以自动配置ConnectionFactory

Artemis configuration is controlled by external configuration properties in spring.artemis.*

配置

一些配置

spring.artemis.embedded.enabled=true # 是否开启内嵌模式, 默认true
spring.artemis.host=localhost # broker 地址默认 localhost
spring.artemis.port=9876
spring.artemis.user=admin
spring.artemis.password=secret
spring.artemis.pool.enabled=false #是否创建 JmsPoolConnectionFactory 而不是 ConnectionFactory; 应该是连接池
spring.artemis.pool.idle-timeout=30 #空闲超时
spring.artemis.pool.max-connections=1 # 连接池最大连接数
spring.artemis.pool.max-sessions-per-connection=500 #池中每个连接最大会话数

# JMS 的
spring.jms.pub-sub-doma=false  # true 为 topic 方式; false 为P2P
spring.jms.template.default-destination # 默认 在没有指定 destination 参数时的默认 Destination

<!>注意一点 如果启用了 spring.artemis.pool.enabled=true连接池, 创建的是JmsPoolConnectionFactory, 使用ConnectionFactory是无法注入的;

然而没有依赖..

<dependency>
    <groupId>org.messaginghub</groupId>
    <artifactId>pooled-jms</artifactId>
</dependency>

CachingConnectionFactory

2.2.7 所有属性配置

加入 @EnableJms 注解

@Configuration
@EnableJms
public class ActiveMQConfig {
}

测试代码

至此 Spring 容器就有 JMS 的ConnectionFactory 了; 还会有一个使用该ConnectionFactoryJmsTemplate

JMS代码

@RestController
@RequestMapping("open/api")
public class OpenController {

	@Autowired
	ConnectionFactory connectionFactory;
	
	@Autowired 
	JmsTemplate  jmsTemplate;
	
	Session session ;
	MessageProducer producer ;

	@RequestMapping("test")
	public ResponseStatus<Map> test() throws Exception {
		Map<String, Object> map = new HashMap<String, Object>();
		Connection connection = connectionFactory.createConnection();
		Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		
        //监听
		Queue orderQueue = session.createQueue("orderer");
		MessageConsumer consumer = session.createConsumer(orderQueue);
		consumer.setMessageListener( new MessageListener() {
			@Override
			public void onMessage(Message message) {
				try {
					String text = message.getBody(String .class);
					System.out.println("consumer === "+ text);
				} catch (JMSException e) {
					e.printStackTrace();
				}
				
			}
		});
		
		MessageProducer producer = session.createProducer(orderQueue);
		this.session = session;
		this.producer = producer;
		//记得开始
		connection.start();
		map.put("static", 0);
		return new ResponseStatus(map);
	}
	
	@RequestMapping("send")
	public ResponseStatus<Map> send() throws JMSException {
		Map<String, Object> map = new HashMap<String, Object>();
		TextMessage message = session.createTextMessage("This is an order 1000001");
		producer.send(message);
	
		map.put("static", 0);
		return new ResponseStatus(map);
	}
	
}

JmsTemplate

还有更简单的 JmsTemplate

@RestController
@RequestMapping("open/api")
public class OpenController {

    @Autowired 
    JmsTemplate  jmsTemplate;

    //监听
    @JmsListener(destination = "orderer" )
	public void newMessage(TextMessage textMsg, Session session) throws JMSException {
		System.out.println("newMessage=="+textMsg.getText() );
		System.out.println("session="+session );
	}

    @RequestMapping("send")
    public ResponseStatus<Map> send() throws JMSException {
        Map<String, Object> map = new HashMap<String, Object>();
        jmsTemplate.send( "orderer", new MessageCreator() {
            @Override
            public Message createMessage(Session session) throws JMSException {
                TextMessage message = session.createTextMessage("This is an order 1000001");
                return message;
            }
        });
        map.put("static", 0);
        return new ResponseStatus(map);
    }
        
	
}

Instances of the JmsTemplate class are thread-safe, once configured. This is important, because it means that you can configure a single instance of a JmsTemplate and then safely inject this shared reference into multiple collaborators. To be clear, the JmsTemplate is stateful, in that it maintains a reference to a ConnectionFactory, but this state is not conversational(会话) state.

in short: JmsTemplate是有状态的,仅用作维护ConnectionFactory, JmsTemplate 还是线程安全的

@Bean
public DefaultJmsListenerContainerFactory myFactory(
        DefaultJmsListenerContainerFactoryConfigurer configurer) {
    DefaultJmsListenerContainerFactory factory =
            new DefaultJmsListenerContainerFactory();
    configurer.configure(factory, connectionFactory());
    factory.setMessageConverter(myMessageConverter());
    return factory;
}

关于 Spring JMS

Spring 文档 Useing Spring JMS

Connections

SingleConnectionFactory
CachingConnectionFactory 默认

DefaultJmsListenerContainerFactory

覆盖 DefaultJmsListenerContainerFactory 的配置;

它是消息监听器的连接工厂, 换句话说这里可以配置,是否手动Ack, 是否开启事务, 配置默认消息转换器;

If you need to create more JmsListenerContainerFactory instances or if you want to override the default, Spring Boot provides a DefaultJmsListenerContainerFactoryConfigurer that you can use to initialize a DefaultJmsListenerContainerFactory with the same settings as the one that is auto-configured.

持久化

找遍文档, 貌似嵌入式只支持持久化到文件,

spring.artemis.embedded.data-directory Journal file directory. Not necessary if persistence is turned off.(持久化存储目录)
spring.artemis.embedded.persistent=false Whether to enable persistent store.(启用持久化)

Spring Boot 非嵌入式 (Artemis)

只能手动配置; 给容器注册 ConnectionFactoryJmsTemplate 根据再注册两个默认的QueueTopic

代码

@Configuration
@ConditionalOnProperty(name = "spring.artemis.embedded.enabled", havingValue = "false")
public class ActiveMQNoEmbedConfig {
	@Bean
	public ConnectionFactory connectionFactory() {
		String url = String.format("tcp://%s:%d", host, port);
		log.info("ActiveMQ 启用非嵌入式配置 {}", url);
		ConnectionFactory connFactory = new ActiveMQConnectionFactory(user, password, url);
		return connFactory;
	}

	@Bean()
	public BrokerControlServer brokerControlServer() throws Exception {
        final String jmxURL =  String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", jmxhost, jmxport);
		BrokerControlServer brokerControlServer = new BrokerControlServer(brokerName, jmxURL);
		return brokerControlServer;
	}

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

	@Bean
	public Topic defaultTopic() {
		return new ActiveMQTopic("topic");
	}
    ...

如何处理大量客户端线程 (How to deal with large number of threads in clients

If you study thread allocation in ActiveMQ clients, you’ll notice that by default there is one thread allocated by every session.默认情况下, 每个会话分配一个线程;
This basically means that session will use its ThreadPoolExecutor to execute its tasks .基本上, 这意味着会话将使用其ThreadPoolExecutor执行其任务; Up until version 5.7 this executor was unbound which could lead to OOM problems in rare case where are a large number of busy sessions in the same JVM could cause uncontrollable spikes in thread creation.

In 5.7 we bounded this executor to a maximum of 1000 threads by default, which we believe should be enough for most use cases. In case of large number of busy sessions, each of them could end up using large number of threads and eventually OOM your application. There are couple of things you can do. The first obvious thing is to decrease the max thread limit a session can use, like this:

在5.7中, 默认情况下, 该执行程序的最大线程限制为1000个, 我们认为对于大多数用例而言, 这已经足够了; 如果有大量繁忙的会话, 则每个会话可能最终会使用大量线程, 并最终使应用程序OOM; 您可以做几件事; 首先显而易见的是减少会话可以使用的最大线程限制

//TODO 貌似 Artemis 不是开一个线程, 连上万都没问题..

官方的性能测试 JMeter performance test

https://activemq.apache.org/jmeter-performance-tests
https://activemq.apache.org/benchmark-tests

都打不开, 看不到具体结果..

Detecting Slow Consumers

探测慢消费者
http://activemq.apache.org/components/artemis/documentation/latest/slow-consumers.html

posted @ 2026-03-28 23:28  daidaidaiyu  阅读(0)  评论(0)    收藏  举报