-
Notifications
You must be signed in to change notification settings - Fork 3
/
Chapter050-Beyond-STOMP.html
153 lines (106 loc) · 13.2 KB
/
Chapter050-Beyond-STOMP.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<section data-type="chapter" id="ch_beyond_stomp" xmlns="http://www.w3.org/1999/xhtml">
<h1>Beyond STOMP</h1>
<p class="lead">STOMP provides a simple yet flexible messaging protocol.<a contenteditable="false" data-primary="STOMP" data-secondary="extending by using additional headers" data-type="indexterm" id="STOMPext"> </a> It also offers an extensible way for brokers to provide additional features beyond the ones specified in the <span class="keep-together">protocol.</span></p>
<p>In this chapter, we will show how to leverage broker features with STOMP headers. Until this chapter, all STOMP brokers could be used to send and receive messages from our applications; but in this chapter, you will have to check your broker documentation to see if it provides these features (or others not covered by this chapter).</p>
<section data-type="sect1" id="_message_persistence">
<h1>Message Persistence</h1>
<p>Some STOMP brokers support <em>persistent</em> messages to ensure that if a message is held by the broker when it<a contenteditable="false" data-primary="messages" data-secondary="persistent, STOMP brokers supporting" data-type="indexterm"> </a> crashes, the message <a contenteditable="false" data-primary="brokers" data-secondary="message persistence" data-type="indexterm"> </a>will be persisted (which means stored on a durable support) so that the broker can fetch it when it restarts and handle it again. This prevents data loss (at the cost of performance, because the broker must ensure that the message is effectively written on the storage support). To use persistent messages, most STOMP brokers (including ActiveMQ) require that the message be sent<a contenteditable="false" data-primary="headers" data-secondary="persistent headers" data-type="indexterm"> </a> with a <code>persistent</code> header set to <code>true</code>.</p>
<p>In the Locations application, we send two types of messages: one for the device location and one for text messages.</p>
<p>The location messages do not need to be persisted and survive a broker crash. There are minimal consequences if these messages are lost if the broker crashed. Once the broker is up again, the device will send an updated position.</p>
<p>However, for the text messages sent to the devices, we want to make sure that they are not lost before the device had a chance to receive it. We could declare these messages as persistent in the Locations web application to ensure that they survive a broker crash:</p>
<pre data-code-language="js" data-type="programlisting">
var order = "XXX";
client.send("/topic/mytopic", { persistent: true}, order);</pre>
<p>When the broker receives the message, it will check the <code>persistent</code> header and persist the message if it is true.</p>
<p>Persisting messages adds a significant cost: the broker must write the message to a durable support (on a filesystem or in a database) and wait for the operating system to effectively write it (some operating systems will buffer data before writing them to disk). The broker must also have sufficient space on the durable support to write the persistent messages (once a persistent message has been delivered to all its consumers, the broker can safely discard it from the durable support).</p>
<p>To add further reliability, the client could use receipts (as described in <a data-type="xref" href="#ch_advanced_stomp_receipts">#ch_advanced_stomp_receipts</a>) to wait until the broker has processed its message (and persisted it). When the receipt is received, the client is guaranteed that the message would survive a server crash. Without a receipt, there is a small window for failure if the server crashes <em>after</em> receiving the message but <em>before</em> storing it on its durable support.</p>
</section>
<section data-type="sect1" id="_filtered_consumer">
<h1>Filtered Consumer</h1>
<p>There are cases in which a consumer may be interested only by a subset of all messages delivered <a contenteditable="false" data-primary="filter, specifying for consumers" data-type="indexterm"> </a>from a destination.<a contenteditable="false" data-primary="consumers" data-secondary="filtered" data-type="indexterm"> </a></p>
<p>Every time a consumer is delivered a message, it can filter it out (using the message’s headers or its payload) to take into account only the interesting ones. However, this is inefficient, because the consumer is still receiving all messages from the destination but may potentially discard many of them.</p>
<p>Some STOMP brokers allow you to specify a filter (or selector) when a consumer subscribes to a destination.<a contenteditable="false" data-primary="brokers" data-secondary="filtered consumers support" data-type="indexterm"> </a> Before delivering the message to the destination’s consumers, the STOMP broker will check whether the message matches the consumer’s filter. The message is delivered to the consumer only if it matches the filter.</p>
<p>A filter is similar to a <a href="http://en.wikipedia.org/wiki/SQL-92">SQL 92</a> conditional query using the message headers. The message payload is opaque for the STOMP broker and cannot be used by filters.</p>
<p>It turns out that this can be quite powerful, because STOMP allows us to add any user-specified header. If we want to filter out messages, we can put interesting information in the headers (either by removing them from the payload or duplicating them).<a contenteditable="false" data-primary="headers" data-secondary="using to filter STOMP messages" data-type="indexterm"> </a></p>
<p>For example, we could add a <code>country</code> header when a location message is sent by the Locations iOS application. The value of this header would correspond to the country where the device is located (e.g., <code>FR</code> for France, <code>DE</code> for Germany, <code>IT</code> for Italy, etc.):</p>
<pre data-code-language="objc" data-type="programlisting">
- (void)sendLocation:(CLLocation *)location
{
// ...
NSString *country = [self findCountryFrom:location];
NSDictionary *headers = @{
@"content-type": @"application/json;charset=utf-8",
@"country": country
};
// send the message
[self.client sendTo:destination
headers:headers
body:body];
}</pre>
<p>With this new header in the message representation, the consumers can now filter out messages to receive only those for certain countries.</p>
<p>If we want to receive only messages from France, Germany, and Italy, we just need to add a <code>selector</code> header when the consumer subscribes to the destination:</p>
<pre data-code-language="js" data-type="programlisting">
client.subscribe(destination, function(message) {
// Only messages with the country header with the value FR, DE, or IT will
// be delivered to the client.
// ...
}, {selector: "country IN ('FR', 'DE', 'IT')"});</pre>
<p>If the STOMP broker support filters consumers and your application can leverage them, this can significantly reduce the amount of messages sent on the network, saving bandwidth, CPU, and battery usage on the client (filtered out messages will not be sent over the network and will not be processed at all by the client).</p>
<div data-type="note">
<p>ActiveMQ has specific rules about how to use a filtered consumer with STOMP, which are described in its <a href="http://bit.ly/activemq-select">Selectors documentation page</a>.</p>
</div>
</section>
<section data-type="sect1" id="_priority">
<h1>Priority</h1>
<p>Messages sent to destinations are usually delivered to a<a contenteditable="false" data-primary="messages" data-secondary="priority" data-type="indexterm"> </a> consumer in the order of their arrival. <a contenteditable="false" data-primary="priority, messages" data-type="indexterm"> </a>This means that if a producer sent messages A, B, and C in that order, a single consumer will receive the messages in the same order: A, B, and C.</p>
<p>However, there are cases where a producer wants to send a more <em>urgent</em> message that should take precedence over messages that it already sent.</p>
<p>Some STOMP brokers provide a way to achieve this using message <em>priority</em>. When a producer sends a message, it can set a <code>priority</code> header to change the message priority.<a contenteditable="false" data-primary="headers" data-secondary="priority headers" data-type="indexterm"> </a> The usual semantics for the priority value are copied from the JMS API, which defines ten levels of priority value, with <code>0</code> as the lowest priority and <code>9</code> as the highest (and <code>4</code> as the default priority).</p>
<p>A broker will often try to deliver expedited messages before messages of lower priority, but this is not a strong requirement. To ensure a <em>fair</em> delivery of messages, it may deliver lower priority messages so that a single producer sending only messages with the highest priority does not prevent the delivery of messages of lower priority from other producers.</p>
<p>In the Locations application, we do not have a use case where changing the priority of a message would make sense, because we use different channels for tracking device location and sending text messages to the device. If we were using a single channel for both kind of messages, we could give the text messages a higher priority (e.g., <code>7</code>) so that they could be delivered before messages with normal priority:</p>
<pre data-code-language="js" data-type="programlisting">
client.send(destination, { priority: 7 }, text);</pre>
</section>
<section data-type="sect1" id="_expiration">
<h1>Expiration</h1>
<p>Messages can be valid for only a given<a contenteditable="false" data-primary="messages" data-secondary="expiration" data-type="indexterm"> </a> duration, after which they are no longer relevant.<a contenteditable="false" data-primary="expiration, messages" data-type="indexterm"> </a> For example, the locations messages sent by the Locations iOS application are valid for a short period of time, after which the device has likely moved to another location.</p>
<p>Some STOMP brokers allow messages to expire after a period of time. The broker will periodically check if messages held by its destinations have expired. If that is the case, it will discard them from the destinations and consumers will not receive them.</p>
<p>ActiveMQ accepts an <code>expires</code> header when a message is sent by a STOMP producer to specify the time until which the message is valid.<a contenteditable="false" data-primary="headers" data-secondary="expires headers" data-type="indexterm"> </a> The value is the number of milliseconds between the <a href="http://en.wikipedia.org/wiki/Unix_time">UNIX time</a> (00:00:00, Thursday, January 1, 1970 UTC) and the expiration date.</p>
<p>For example, if we want to expire messages 10 minutes after they are sent by the <code>Locations</code> applications, we need to add an <code>expires</code> header with a value that is the number of milliseconds since the Unix time and ten minutes after now:</p>
<pre data-code-language="objc" data-type="programlisting">
- (void)sendLocation:(CLLocation *)location
{
// ...
// 10 minutes from now
NSTimeInterval expiration = [[NSDate date] timeIntervalSince1970] + 600000;
NSDictionary *headers = @{
@"content-type": @"application/json;charset=utf-8",
@"expires": [NSNumber numberWithLong:(long)expiration]
};
// send the message
[self.client sendTo:destination
headers:headers
body:body];
}</pre>
<p>Expiring messages can improve the health of your applications. Producers have no knowledge of when their messages will be consumed and by whom. However, if they know that the data sent in their messages has a limited lifetime, they can expire them after a given time instead of letting the broker deliver them to consumers after they stop being valid.</p>
</section>
<section data-type="sect1" id="_summary_5">
<h1>Summary</h1>
<p>In this chapter, we learned that STOMP is a simple and flexible protocol that can be extended by brokers and clients using additional headers.</p>
<p>Based on the ActiveMQ broker, we see that STOMP can be extended to:</p>
<ul>
<li>
<p>Support <em>persistent</em> messages by passing a <code>persistent</code> header set to <code>true</code> when messages are sent.</p>
</li>
<li>
<p>Create a <em>filtered consumer</em> use a <code>selector</code> filter to receive only messages with headers that match the filter.</p>
</li>
<li>
<p>Send messages with a higher or lower priority using the <code>priority</code> header.</p>
</li>
<li>
<p>Priority messages after an expiration date so that broker will not deliver messages after this date.</p>
</li>
</ul>
<p>Depending on the STOMP broker you use, you may be able to use these features or others to improve the design of your architecture and reduce the network bandwidth and the CPU and battery usage so that producers and consumers only deal with relevant messages and ignore the others.<a contenteditable="false" data-primary="STOMP" data-secondary="extending by using additional headers" data-startref="STOMPext" data-type="indexterm"> </a></p>
</section>
</section>