Chapter 23. 大消息

HornetQ支持超大消息的发送和接收。消息的大小不受客户端或服务器端的内存限制。它只受限于你的磁盘空间的大小。 在我们作过的测试中,消息最大可达8GiB,而客户端和服务器端的内存只有50MiB!

要发送一个大消息,用户需要为大消息提供一个InputStream,当大消息被发送时, HornetQ从该InputStream读取消息。例如,要将一个磁盘中的大文件以消息形式发送,可以 使用FileInputStream

数据从InputStream读出并分解为一个个数据片段向服务器以流的形式发送。服务器在收到 这些片段后将它们保存到磁盘上。当服务器准备向接收者传递消息时,它将这些片段读回,同样以片段流的形式向接收者 一端发送。当接收者开始接收时,最初收到的只是一个空的消息体。它需要为其设置一个OutputStream 以便向大消息保存到磁盘上或其它地方。从发送到接收整个过程中不需要整个消息都在内存中。

23.1. 服务器端的配置

大消息在服务器端是直接保存在磁盘目录中。这一目录可以在HornetQ的配置文件中定义。

这个参数的名字是large-messages-directory

<configuration xmlns="urn:hornetq"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="urn:hornetq /schema/hornetq-configuration.xsd">

...

<large-message-directory>/data/large-messages</large-message-directory>

...

</configuration

默认的大消息保存目录是data/largemessages

为了提高性能,我们建议将大消息的保存目录定义到与消息日志(journal)或分页转存目录分开的物理卷上。

23.2. 设定限制

参数min-large-message-size定义了大消息的最小值。 任何消息的大小如果超过了该值就被视为大消息。一旦成为大消息,它将被分成小的 片段来传送。

默认值是100KiB.

23.2.1. 使用核心的API

如果使用HornetQ的核心,ClientSessionFactory.setMinLargeMessageSize方法 可以设置大消息的最小值。

ClientSessionFactory factory = 
            HornetQClient.createClientSessionFactory(new 
            TransportConfiguration(NettyConnectorFactory.class.getName()), null);
factory.setMinLargeMessageSize(25 * 1024);

Section 16.3, “在客户端直接配置传输层”对于如何实例化一个会话工厂(session factory) 给出了进一步的说明。

23.2.2. 使用JMS

如果连接工厂是通过JNDI方式获得的,则需要在hornetq-jms.xml文件中定义:

...
<connection-factory name="ConnectionFactory">
<connectors>
   <connector-ref connector-name="netty"/>
</connectors>
<entries>
   <entry name="ConnectionFactory"/>
   <entry name="XAConnectionFactory"/>
</entries>
                
<min-large-message-size>250000</min-large-message-size>
</connection-factory>
...

如果是直接实例化连接工厂,则使用HornetQConnectionFactory.setMinLargeMessageSize方法来定义。

23.3. 大消息与流(stream)

在HornetQ中可以定义大消息所使用的输入和输出流(java.lang.io)。

HornetQ将使用定义的流来发送(输入流)和接收(输出流)大消息。

在使用输出流接收大消息时,有两种选择:你可以用ClientMessage.saveOutputStream方法 以阻塞的方式保存大消息;或者你可以使用ClientMessage.setOutputstream方法 以异步方法保存大消息。在采用后一种方法时,必须保证接收者(consumer)在大消息的接收过程中保持 有效状态。

根据需要选择所适合的流。最常见的情况是将磁盘文件以消息方式发送,也有可能是JDBC的Blob数据, 或者是一个SocketInputStream,或是来自HTTPRequests 的数据等等。只要是实现了java.io.InputStreamjava.io.OutputStream的数据源都可以作为大消息传送。

23.3.1. 核心API中流的使用

下表列出了ClientMessage上可以使用的方法。 通过相应的对象属性也可以在JMS中应用。

Table 23.1. org.hornetq.api.core.client.ClientMessage API

名称说明JMS相对应的属性
setBodyInputStream(InputStream)设定大消息发送时所使用的输入流。JMS_HQ_InputStream
setOutputStream(OutputStream)设定异步接收大消息所使用的输出流。JMS_HQ_OutputStream
saveOutputStream(OutputStream)设定保存大消息所使用的输出流。这个方法将会阻塞直到大消息全部 保存完毕才返回。JMS_HQ_SaveStream

下面代码中设定了接收核心消息所用的输出流:

...
ClientMessage msg = consumer.receive(...);


// This will block here until the stream was transferred
msg.saveOutputStream(someOutputStream); 

ClientMessage msg2 = consumer.receive(...);

// This will not wait the transfer to finish
msg.setOutputStream(someOtherOutputStream); 
...
                
            

设定发送核心消息所用的输入流:

...
ClientMessage msg = session.createMessage();
msg.setInputStream(dataInputStream);
...
            

23.3.2. 在JMS中使用流

使用JMS时,HornetQ根据定义的属性值调用对应的核心接口(参见 Table 23.1, “org.hornetq.api.core.client.ClientMessage API”)来使用流。你只需要用 Message.setObjectProperty方法设置适当的输入/输出流即可。

输入流InputStream可以通过JMS属性JMS_HQ_InputStream来定义:

BytesMessage message = session.createBytesMessage();

FileInputStream fileInputStream = new FileInputStream(fileInput);

BufferedInputStream bufferedInput = new BufferedInputStream(fileInputStream);

message.setObjectProperty("JMS_HQ_InputStream", bufferedInput);

someProducer.send(message);

输出流OutputStream可以通过JMS属性JMS_HQ_SaveStream来定义。下面是阻塞式方法:

BytesMessage messageReceived = (BytesMessage)messageConsumer.receive(120000);
                
File outputFile = new File("huge_message_received.dat");
                
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
                
BufferedOutputStream bufferedOutput = new BufferedOutputStream(fileOutputStream);
                
// This will block until the entire content is saved on disk
messageReceived.setObjectProperty("JMS_HQ_SaveStream", bufferedOutput);
            

也可以使用JMS_HQ_OutputStream属性以非阻塞式(异步)方法来定义输出流OutputStream

// This won't wait the stream to finish. You need to keep the consumer active.
messageReceived.setObjectProperty("JMS_HQ_OutputStream", bufferedOutput);
            

Note

使用JMS时,只有StreamMessageBytesMessage才支持大消息的传送。

23.4. 不使用流的方式

如果不想使用输入流与输出流来传送大消息,可以用另外一种方法。

使用核心接口时,可以直接从消息中读字节。

ClientMessage msg = consumer.receive();
         
byte[] bytes = new byte[1024];
for (int i = 0 ;  i < msg.getBodySize(); i += bytes.length)
{
   msg.getBody().readBytes(bytes);
   // Whatever you want to do with the bytes
}

使用JMS接口时,BytesMessageStreamMessage 本身提供这样的支持。

BytesMessage rm = (BytesMessage)cons.receive(10000);

byte data[] = new byte[1024];

for (int i = 0; i < rm.getBodyLength(); i += 1024)
{
   int numberOfBytes = rm.readBytes(data);
   // Do whatever you want with the data
}        

23.5. 在客户端缓存大消息

大消息通过流在服务器和客户端之间传输。每个大消息被分割成很多小的数据包传递。因此大消息只能被 读取一次。这样一个大消息在收到后就不能再被再次传送。例如,JMS Bridge在发送大消息时如果在出现故障, 将不能把它重新发送。

要解决这个问题,可以在连接工厂上设置cache-large-message-client属性。 这个属性可以使客户端接收者创建一个临时的文件保存收到的大消息,这样就可以在需要时能够重新发送该消息。

Note

如果JMS Bridge用来发送大消息,可以在它使用的连接工厂上使用它。

23.6. 大消息例子

我们在Section 11.1.22, “大消息”提供了一个在JMS中配置和使用大消息的例子。