DATAKV-22

+ add Redis container documentation
This commit is contained in:
Costin Leau
2011-01-18 20:31:07 +02:00
parent ba55d19c7f
commit 0a5fb3029c
3 changed files with 184 additions and 1 deletions

View File

@@ -16,7 +16,7 @@
<junit.version>4.8.1</junit.version>
<log4j.version>1.2.15</log4j.version>
<org.codehaus.jackson.version>1.6.1</org.codehaus.jackson.version>
<org.mockito.version>1.8.4</org.mockito.version>
<org.mockito.version>1.8.5</org.mockito.version>
<org.slf4j.version>1.5.8</org.slf4j.version>
<org.spockframework.version>0.5-groovy-1.7-SNAPSHOT</org.spockframework.version>
<org.springframework.version>3.0.5.RELEASE</org.springframework.version>

View File

@@ -5,5 +5,6 @@
offered by Spring Data Key Value.</para>
<para><xref linkend="redis"/> introduces the Redis module feature set.</para>
<para><xref linkend="riak"/> introduces the Riak module feature set.</para>
</partintro>

View File

@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<section>
<section id="redis:pubsub:intro">
<title>Introduction</title>
<para>Spring Data provides dedicated messaging integration for Redis,
very similar in functionality and naming to the JMS integration in
Spring Framework; in fact, users familiar with the JMS support in Spring, should
feel right at home.</para>
<para>Redis messaging can be roughly divided into two areas of functionality, namely
the production or publication and consumption or subscription of messages, hence the shortcut
pubsub (Publish/Subscribe). The
<classname>RedisTemplate</classname> class is used for message production.
For asynchronous reception similar to
Java EE's message-driven bean style, Spring Data provides a dedicated message
listener containers that is used to create Message-Driven POJOs
(MDPs) and for synchronous reception, the <interfacename>RedisConnection</interfacename> contract.</para>
<para>The package <literal>org.springframework.data.keyvalue.redis.connection</literal> and
<literal>org.springframework.data.keyvalue.redis.listener</literal> provide
the core functionality for using Redis messaging.</para>
</section>
<section id="redis:pubsub:publish">
<title>Sending/Publishing messages</title>
<para>To publish a message, one can use, as with the other operations, either the low-level
<interfacename>RedisConnection</interfacename> or the high-level <classname>RedisTemplate</classname>.
Both entities offer the <methodname>publish</methodname> method that accepts as argument the message
that needs to be sent as well as the destination channel. While <interfacename>RedisConnection</interfacename>
requires raw-data (array of bytes), the <classname>RedisTemplate</classname> allow arbitrary objects to be passed
in as messages:</para>
<programlisting lang="java"><![CDATA[// send message through connection
RedisConnection con = ...
byte[] msg = ...
byte[] channel = ...
con.publish(msg, channel);
// send message through RedisTemplate
RedisTemplate template = ...
template.publish("hello!", "world");]]></programlisting>
</section>
<section id="redis:pubsub:subscribe">
<title>Receiving/Subscribing for messages</title>
<para>On the receiving side, one can subscribe to one or multiple channels either by naming them directly or by using
pattern matching. The latter approach is quite useful as it not only allows multiple subscriptions to be created with
one command but to also listen on channels not yet created at subscription time (as long as match the pattern).
</para>
<para>At the low-level, <interfacename>RedisConnection</interfacename> offers <methodname>subscribe</methodname> and
<methodname>pSubscribe</methodname> methods that map the Redis commands for subscribing by channel respectively by pattern.
Note that multiple channels or patterns can be used as arguments. To change the subscription of a connection or simply query
whether it is listening or not, <interfacename>RedisConnection</interfacename>
provides <methodname>getSubscription</methodname> and <methodname>isSubscribed</methodname> method.</para>
<important>Subscribing commands are synchronized and thus blocking. That is, calling subscribe on a connection will cause
the current thread to block as it will start waiting for messages - the thread will be released only if the subscription
is canceled, that is an additional thread invokes <methodname>unsubscribe</methodname> respectively <methodname>pUnsubscribe</methodname>
on the <emphasis>same</emphasis> connection.</important>
<para>As mentioned above, one subscribed a connection starts waiting for messages - no other commands can be invoked on it except
for adding new subscriptions or modifying/canceling the existing ones, that is invoking anything else then <methodname>subscribe</methodname>,
<methodname>pSubscribe</methodname>, <methodname>unsubscribe</methodname>, <methodname>pUnsubscribe</methodname> or is illegal and will
through an exception.</para>
<para>In order to subscribe for messages, one needs to implement the <interfacename>MessageListener</interfacename> callback: each time
a new message arrives, the callback gets invoked and the user code executed through <methodname>onMessage</methodname> method.
The interface gives access not only to the actual message but to the channel it has been received through and the pattern (if any) used
by the subscription to match the channel. This information allows the callee to differentiate between various messages not just by content but
also through data.
</para>
<section id="redis:pubsub:subscribe:containers">
<title>Message Listener Containers</title>
<para>Due to its blocking nature, low-level subscription is not attractive as it requires connection and thread management for every single
listener. To alleviate this problem, Spring Data offers <classname>RedisListenerContainer</classname> which does all the heavy lifting
on behalf of the user - users familiar with EJB and JMS should find the concepts familiar as it is designed as close as possible to the
support in Spring Framework and its message-driven POJOs (MDPs)</para>
<para><classname>RedisListenerContainer</classname> acts as a message listener container; it is used to receive messages from a
Redis channel and drive the MessageListener that are injected into
it. The listener container is responsible for all threading of message
reception and dispatches into the listener for processing. A message
listener container is the intermediary between an MDP and a messaging
provider, and takes care of registering to receive messages, resource acquisition and release,
exception conversion and suchlike. This allows you as an application
developer to write the (possibly complex) business logic associated with
receiving a message (and reacting to it), and delegates
boilerplate Redis infrastructure concerns to the framework.
</para>
<para>Further more, to minimize the application footprint, <classname>RedisListenerContainer</classname> performs allows one connection and one thread
to be shared by multiple listeners even though they do not share a subscription. Thus no matter how many listeners or channels an application tracks,
the runtime cost will remain the same through out its lifetime. Moreover, the container allows runtime configuration changes so one can add or remove
listeners while an application is running without the need for restart. Additionally, the container uses a lazy subscription approach, using a
<interfacename>RedisConnection</interfacename> only when needed - if all the listeners are unsubscribed, cleanup is automatically performed and the used
thread released.</para>
<para>To help with the asynch manner of messages, the container requires a <interfacename>java.util.concurrent.Executor</interfacename> (
or Spring's <interfacename>TaskExecutor</interfacename>) for dispatching the messages. Depending on the load, the number of listeners or the runtime
environment, one should change or tweak the executor to better serve her needs - in particular in managed environments (such as app servers), it is
highly recommended to pick a a proper <interfacename>TaskExecutor</interfacename> to take advantage of its runtime.</para>
</section>
<section id="redis:pubsub:subscribe:adapter">
<title>The <classname>MessageListenerAdapter</classname></title>
<para>The <classname>MessageListenerAdapter</classname> class is the
final component in Spring's asynchronous messaging support: in a
nutshell, it allows you to expose almost <emphasis>any</emphasis> class
as a MDP (there are of course some constraints).</para>
<para>Consider the following interface definition. Notice that although
the interface extends the
<interfacename>MessageListener</interfacename> interface,
it can still be used as a MDP via the use of the
<classname>MessageListenerAdapter</classname> class. Notice also how the
various message handling methods are strongly typed according to the
<emphasis>contents</emphasis> of the various
<interfacename>Message</interfacename> types that they can receive and
handle.</para>
<programlisting language="java">public interface MessageDelegate {
void handleMessage(String message);
void handleMessage(Map message);
void handleMessage(byte[] message);
void handleMessage(Serializable message);
}</programlisting>
<programlisting language="java">public class DefaultMessageDelegate implements MessageDelegate {
<lineannotation>// implementation elided for clarity...</lineannotation>
}</programlisting>
<para>In particular, note how the above implementation of the
<interfacename>MessageDelegate</interfacename> interface (the above
<classname>DefaultMessageDelegate</classname> class) has
<emphasis>no</emphasis> Redis dependencies at all. It truly is a POJO that
we will make into an MDP via the following configuration.</para>
<programlisting language="xml"><lineannotation>&lt;!-- this is the Message Driven POJO (MDP) --&gt;</lineannotation>
<emphasis role="bold">&lt;bean id="messageListener" class="org.springframework.data.keyvalue.redis.listener.adapter.MessageListenerAdapter"&gt;</emphasis>
&lt;constructor-arg&gt;
&lt;bean class="redisexample.DefaultMessageDelegate"/&gt;
&lt;/constructor-arg&gt;
&lt;/bean&gt;
<lineannotation>&lt;!-- and this is the message listener container... --&gt;</lineannotation>
&lt;bean id="redisContainer" class="org.springframework.data.keyvalue.redis.listener.RedisMessageListenerContainer"&gt;
&lt;property name="connectionFactory" ref="connectionFactory"/&gt;
&lt;property name="messageListeners"&gt;
<lineannotation>&lt;!-- map of listeners and their associated topics (channels or topics) --&gt;</lineannotation>
&lt;map&gt;
<emphasis role="bold">&lt;entry key-ref="messageListener"&gt;</emphasis>
&lt;bean class="org.springframework.data.keyvalue.redis.listener.ChannelTopic">
&lt;constructor-arg value="chatroom"&gt;
&lt;/bean&gt;
&lt;/entry&gt;
&lt;/map&gt;
&lt;/property&gt;
&lt;/bean&gt;</programlisting>
<para>Each time a message is received, the adapter automatically performs
translation (using the configured <interfacename>RedisSerializer</interfacename>)
between the low-level format and the required object type transparently. Any exception caused by the method invocation
is caught and handled by the container (by default, being logged).
</para>
</section>
</section>
</section>