Spring Data Redis

来源:互联网 发布:网络利大于弊辩论会 编辑:程序博客网 时间:2024/06/05 00:36

5.9. Redis Messaging/PubSub

Spring Data 提供 dedicated messaging 整合 for Redis, very similar in functionality and naming to the JMS integration in Spring Framework; 事实上, 用户熟悉 the JMS support in Spring should feel right at home.

Redis 消息 can be roughly 分成 two areas of functionality, namely the production or publication and consumption or subscription of messages, 因此简称为pubsub (Publish/Subscribe). RedisTemplate类 is used for message production. 对于异步的接收如同 Java EE’s message-driven bean style, Spring Data provides a dedicated message listener container that is used to create Message-Driven POJOs (MDPs) and for synchronous reception, the RedisConnection contract.

The package org.springframework.data.redis.connection and org.springframework.data.redis.listener provide the core functionality for using Redis messaging.

5.9.1. Sending/Publishing messages

要发布一个消息, one can use, as with the other operations, 要么使用低级别的RedisConnection 要么使用高级的RedisTemplate. Both entities offer the publish method that accepts as an argument the message that needs to be sent as well as the destination channel. While RedisConnection requires raw-data (array of bytes), the RedisTemplate allow arbitrary objects to be passed in as messages:

// 1.send message through connection RedisConnection con = ...byte[] msg = ...byte[] channel = ...con.publish(msg, channel);// 2.send message through RedisTemplateRedisTemplate template = ...template.convertAndSend("hello!", "world");

5.9.2. Receiving/Subscribing for messages

在接收端, 一端可以订阅一个或多个要么通过直接命名的方式,要么通过模式匹配的方式。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 (只要他们匹配模式).

在低级别中, RedisConnection 提供 subscribe and pSubscribe 方法,匹配Redis命令行中的订阅命令 that map the Redis commands for subscribing by channel respectively by pattern. 注意,多样的频道和模式 can be used as arguments. To change the subscription of a connection or simply query whether it is listening or not, RedisConnection provides getSubscription and isSubscribed方法.

Subscription命令在Spring Data Redis中是阻塞的. 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 `unsubscribe` or `pUnsubscribe` on the same connection. See message listener container below for a solution to this problem.

上面说道的, 一旦订阅连接等待消息. No other commands can be invoked on it except for adding new subscriptions or modifying/canceling the existing ones. That is, invoking anything other then subscribe, pSubscribe, unsubscribe, or pUnsubscribe is illegal and will throw an exception.

为了订阅消息, 接收端需要实现接口MessageListener回调: 每次一个新消息到达, 回调获得调用并且用户代码通过通过onMessage方法执行. 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.

Message Listener Containers

由于它的阻塞类型, low-level subscription is not attractive as it requires connection and thread management for every single listener. To alleviate this problem, Spring Data offers RedisMessageListenerContainer 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)

RedisMessageListenerContainer作为一个消息监听容器执行; 它用来从一个Redis频道中接收消息并且drive the MessageListener s 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 the like. 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.

而且, to minimize the application 占用的空间, RedisMessageListenerContainer 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 RedisConnection only when needed - if all the listeners are unsubscribed, cleanup is automatically performed and the used thread released.

To help with the asynch manner of messages, the container requires a java.util.concurrent.Executor ( or Spring’s TaskExecutor) 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 TaskExecutor to take advantage of its runtime.

The MessageListenerAdapter

MessageListenerAdapter 类是最终的部分 in Spring’s asynchronous messaging support: in a nutshell, it allows you to expose almost any class as a MDP (there are of course some constraints).

考虑下面的接口定义. 注意尽管这个接口没有继承MessageListener接口, it can still be used as a MDP via the use of the MessageListenerAdapter类. Notice also how the various message handling methods are strongly typed according to the contents of the various Message types that they can receive and handle. 另外, the channel or pattern to which a message is sent can be passed in to the method as the second argument of type String:

public interface MessageDelegate {  void handleMessage(String message);  void handleMessage(Map message); void handleMessage(byte[] message);  void handleMessage(Serializable message);  // pass the channel/pattern as well  void handleMessage(Serializable message, String channel); }
public class DefaultMessageDelegate implements MessageDelegate {  // implementation elided for clarity...}

尤其地, 注意上面的实现类是如何实现MessageDelegate 接口的 (上面的 DefaultMessageDelegate 类) 根本没有依赖Redis. It truly is a POJO that we will make into an MDP 通过下面的配置.

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   xmlns:redis="http://www.springframework.org/schema/redis"   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd   http://www.springframework.org/schema/redis http://www.springframework.org/schema/redis/spring-redis.xsd"><!-- the default ConnectionFactory --><redis:listener-container>  <!-- the method attribute can be skipped as the default method name is "handleMessage" -->  <redis:listener ref="listener" method="handleMessage" topic="chatroom" /></redis:listener-container><bean id="listener" class="redisexample.DefaultMessageDelegate"/> ...<beans>
主题监听器可以是一个频道 (e.g. topic="chatroom") or a pattern (e.g. topic="*room") 

上面的例子使用Redis 命名空间来声明消息监听容器并且自动注册 the POJOs作为监听器. The full blown, bean的定义展示如下:

<bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">  <constructor-arg>    <bean class="redisexample.DefaultMessageDelegate"/>  </constructor-arg></bean><bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">  <property name="connectionFactory" ref="connectionFactory"/>  <property name="messageListeners">    <map>      <entry key-ref="messageListener">        <bean class="org.springframework.data.redis.listener.ChannelTopic">          <constructor-arg value="chatroom">        </bean>      </entry>    </map>  </property></bean>

每次接收一个消息, 适配器自动地执行转换 (using the configured RedisSerializer) 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).

0 0