# Redis 支持

# Redis 支持

Spring Integration2.1 引入了对Redis (opens new window)的支持:“一个开源的高级键值存储”。这种支持以基于 Redis 的MessageStore以及发布-订阅消息适配器的形式出现,Redis 通过其[PUBLISHSUBSCRIBEUNSUBSCRIBE](https://redis.io/topics/pubsub)命令支持这些消息适配器。

你需要在项目中包含此依赖项:

Maven

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-redis</artifactId>
    <version>5.5.9</version>
</dependency>

Gradle

compile "org.springframework.integration:spring-integration-redis:5.5.9"

你还需要包括 Redis 客户机依赖项,例如 Lettuce。

要下载、安装和运行 Redis,请参见Redis 文档 (opens new window)

# 连接到 Redis

要开始与 Redis 交互,你首先需要连接到它。 Spring 集成使用另一个 Spring 项目Spring Data Redis (opens new window)提供的支持,该项目提供了典型的 Spring 构造:ConnectionFactoryTemplate。这些抽象简化了与多个 Redis 客户机 Java API 的集成。目前 Spring 数据 Redis 支持Jedis (opens new window)Lettuce (opens new window)

# 使用RedisConnectionFactory

要连接到 Redis,可以使用RedisConnectionFactory接口的一种实现。下面的清单显示了接口定义:

public interface RedisConnectionFactory extends PersistenceExceptionTranslator {

    /**
     * Provides a suitable connection for interacting with Redis.
     * @return connection for interacting with Redis.
     */
    RedisConnection getConnection();
}

下面的示例展示了如何在 Java 中创建LettuceConnectionFactory:

LettuceConnectionFactory cf = new LettuceConnectionFactory();
cf.afterPropertiesSet();

下面的示例展示了如何在 Spring 的 XML 配置中创建LettuceConnectionFactory:

<bean id="redisConnectionFactory"
    class="o.s.data.redis.connection.lettuce.LettuceConnectionFactory">
    <property name="port" value="7379" />
</bean>

RedisConnectionFactory的实现提供了一组属性,例如端口和主机,你可以在需要时对其进行设置。一旦有了RedisConnectionFactory的实例,就可以创建RedisTemplate的实例,并将其注入RedisConnectionFactory

# 使用RedisTemplate

与 Spring 中的其他模板类(例如JdbcTemplateJmsTemplate)一样,RedisTemplate是一个帮助类,它简化了 Redis 数据访问代码。有关RedisTemplate及其变体(例如StringRedisTemplate)的更多信息,请参见Spring Data Redis documentation (opens new window)

下面的示例展示了如何在 Java 中创建RedisTemplate实例:

RedisTemplate rt = new RedisTemplate<String, Object>();
rt.setConnectionFactory(redisConnectionFactory);

下面的示例展示了如何在 Spring 的 XML 配置中创建RedisTemplate实例:

<bean id="redisTemplate"
         class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="redisConnectionFactory"/>
</bean>

# 使用 Redis 进行消息传递

正如导言中提到的,Redis 通过其PUBLISHSUBSCRIBEUNSUBSCRIBE命令提供对发布-订阅消息的支持。 Spring 与 JMS 和 AMQP 一样,集成为通过 Redis 发送和接收消息提供了消息通道和适配器。

# Redis 发布/订阅频道

与 JMS 类似,在某些情况下,生产者和消费者都是同一个应用程序的一部分,在同一个流程中运行。你可以通过使用一对入站和出站通道适配器来实现这一点。然而,与 Spring Integration 的 JMS 支持一样,有一种更简单的方法来解决这个用例。你可以创建一个发布-订阅通道,如下例所示:

<int-redis:publish-subscribe-channel id="redisChannel" topic-name="si.test.topic"/>

publish-subscribe-channel的行为很像来自主 Spring 集成名称空间的正常<publish-subscribe-channel/>元素。任何端点的input-channeloutput-channel属性都可以引用它。不同之处在于,该通道由一个 Redis 主题名称支持:由topic-name属性指定的String值。然而,与 JMS 不同的是,这个主题不必预先创建,甚至不必由 Redis 自动创建。在 Redis 中,主题是起地址作用的简单String值。生产者和消费者可以通过使用相同的String值作为其主题名称进行通信。对此通道的简单订阅意味着在产生端点和使用端点之间可以进行异步发布-订阅消息传递。然而,与通过在简单的 Spring 集成<channel/>元素中添加<queue/>元素创建的异步消息通道不同,消息不存储在内存队列中。相反,这些消息是通过 Redis 传递的,它让你依赖于它对持久性和集群的支持,以及它与其他非 Java 平台的互操作性。

# Redis 入站通道适配器

Redis 入站通道适配器(RedisInboundChannelAdapter)以与其他入站适配器相同的方式将传入的 Redis 消息调整为 Spring 消息。它接收特定于平台的消息(本例中为 Redis),并使用MessageConverter策略将它们转换为 Spring 消息。下面的示例展示了如何配置 Redis 入站通道适配器:

<int-redis:inbound-channel-adapter id="redisAdapter"
       topics="thing1, thing2"
       channel="receiveChannel"
       error-channel="testErrorChannel"
       message-converter="testConverter" />

<bean id="redisConnectionFactory"
    class="o.s.data.redis.connection.lettuce.LettuceConnectionFactory">
    <property name="port" value="7379" />
</bean>

<bean id="testConverter" class="things.something.SampleMessageConverter" />

前面的示例展示了 Redis 入站通道适配器的简单但完整的配置。请注意,前面的配置依赖于熟悉的自动发现某些 bean 的范例 Spring。在这种情况下,redisConnectionFactory被隐式地注入到适配器中。你可以使用connection-factory属性来显式地指定它。

另外,请注意,前面的配置向适配器注入了自定义MessageConverter。这种方法类似于 JMS,其中MessageConverter实例用于在 Redis 消息和 Spring 集成消息有效负载之间进行转换。默认值是SimpleMessageConverter

入站适配器可以订阅多个主题名称,因此在topics属性中使用逗号分隔的值集。

自 3.0 版本以来,入站适配器除了现有的topics属性外,现在还具有topic-patterns属性。此属性包含一组以逗号分隔的 Redis 主题模式。有关 Redis 发布-订阅的更多信息,请参见Redis Pub/Sub (opens new window)

入站适配器可以使用RedisSerializer来反序列化 Redis 消息体。可以将<int-redis:inbound-channel-adapter>属性的serializer属性设置为一个空字符串,这将为null属性生成一个null值。在这种情况下,REDIS 消息的 RAWbyte[]主体被提供为消息有效负载。

从版本 5.0 开始,你可以使用<int-redis:inbound-channel-adapter>task-executor属性向入站适配器提供Executor实例。另外,接收到的 Spring 集成消息现在具有RedisHeaders.MESSAGE_SOURCE头,以指示已发布消息的源:topic 或 pattern。你可以将此下游用于路由逻辑。

# Redis 出站通道适配器

Redis 出站通道适配器以与其他出站适配器相同的方式将出站 Spring 集成消息调整为 Redis 消息。它接收 Spring 集成消息,并通过使用MessageConverter策略将它们转换为特定于平台的消息(在本例中为 Redis)。下面的示例展示了如何配置 Redis 出站通道适配器:

<int-redis:outbound-channel-adapter id="outboundAdapter"
    channel="sendChannel"
    topic="thing1"
    message-converter="testConverter"/>

<bean id="redisConnectionFactory"
    class="o.s.data.redis.connection.lettuce.LettuceConnectionFactory">
    <property name="port" value="7379"/>
</bean>

<bean id="testConverter" class="things.something.SampleMessageConverter" />

该配置与 Redis 入站通道适配器并行。适配器隐式地注入了一个RedisConnectionFactory,它以redisConnectionFactory作为其 Bean 名称。这个示例还包括可选的(和自定义的)MessageConvertertestConverter Bean)。

Spring Integration3.0 之后,<int-redis:outbound-channel-adapter>提供了topic属性的替代方案:你可以使用topic-expression属性在运行时确定消息的 Redis 主题。这些属性是相互排斥的。

# Redis 队列入站通道适配器

Spring Integration3.0 引入了队列入站通道适配器,以从 Redis 列表中“弹出”消息。默认情况下,它使用“右 POP”,但你可以将其配置为使用“左 POP”。适配器是消息驱动的。它使用内部侦听器线程,而不使用 Poller。

下面的清单显示了queue-inbound-channel-adapter的所有可用属性:

<int-redis:queue-inbound-channel-adapter id=""  (1)
                    channel=""  (2)
                    auto-startup=""  (3)
                    phase=""  (4)
                    connection-factory=""  (5)
                    queue=""  (6)
                    error-channel=""  (7)
                    serializer=""  (8)
                    receive-timeout=""  (9)
                    recovery-interval=""  (10)
                    expect-message=""  (11)
                    task-executor=""  (12)
                    right-pop=""/>  (13)
1 组件 Bean name.
如果不提供channel属性,则在应用程序上下文中创建并注册一个DirectChannel,并将该id属性作为 Bean 名称。
在这种情况下,端点本身以 Bean 名称id加上.adapter注册。
(如果 Bean 名称是thing1,则端点注册为thing1.adapter。)
2 从该端点向其发送MessageChannel实例的Message实例。
3 一个SmartLifecycle属性,用于指定此端点是否应在应用程序上下文启动后自动启动。
它默认为true
4 一个SmartLifecycle属性来指定这个端点启动的阶段。
它默认为0
5 RedisConnectionFactory Bean.
的引用默认为redisConnectionFactory
6 执行基于队列的“POP”操作以获取 Redis 消息的 Redis 列表的名称。
7 当从端点的侦听任务接收到异常时,向其发送MessageChannel实例。
默认情况下,底层MessagePublishingErrorHandler使用应用程序上下文中的默认errorChannel
8 RedisSerializer Bean 引用。
它可以是一个空字符串,这意味着’没有序列化器’。
在这种情况下,来自入站 Redis 消息的原始byte[]被发送到channel作为Message有效载荷。
默认情况下它是JdkSerializationRedisSerializer
9 “pop”操作等待来自队列的 Redis 消息的超时(以毫秒为单位)。
默认值为 1 秒。
10 侦听器任务在“pop”操作出现异常后,在重新启动侦听器任务之前,应该睡眠的时间,以毫秒为单位。
11 指定此端点是否期望来自 Redis 队列的数据包含整个Message实例。
如果将此属性设置为true,则serializer不能是空字符串,因为消息需要某种形式的反序列化(默认情况下为 JDK 序列化)。
它的默认值是false
12 对 Spring TaskExecutor(或标准 JDK1.5+Executor) Bean 的引用。
它用于底层监听任务。
它默认为SimpleAsyncTaskExecutor
13 指定此端点是否应该使用“右 POP”(当true时)或“左 POP”(当false时)从 Redis 列表中读取消息,
如果true,当与默认的 Redis 队列出站通道适配器一起使用时,Redis 列表充当FIFO队列。
将其设置为false以便与软件一起使用它以“右推”或实现堆栈式消息顺序写入列表。
它的默认值是true
自版本 4.3 起。
task-executor必须配置多个线程进行处理;否则,当RedisQueueMessageDrivenEndpoint在出现错误后试图重新启动侦听器任务时,可能会出现死锁,
errorChannel可以用来处理这些错误,以避免重新启动,但最好不要将你的应用程序暴露在可能的死锁情况下。
有关可能的TaskExecutor实现,请参见 Spring Framework参考手册 (opens new window)

# Redis 队列出站通道适配器

Spring Integration3.0 引入了队列出站通道适配器,以从 Spring Integration 消息“推送”到 Redis 列表。默认情况下,它使用“左推”,但你可以将其配置为使用“右推”。下面的清单显示了 Redisqueue-outbound-channel-adapter的所有可用属性:

<int-redis:queue-outbound-channel-adapter id=""  (1)
                    channel=""  (2)
                    connection-factory=""  (3)
                    queue=""  (4)
                    queue-expression=""  (5)
                    serializer=""  (6)
                    extract-payload=""  (7)
                    left-push=""/>  (8)
1 组件 Bean name.
如果你不提供channel属性,那么将在应用程序上下文中创建并注册一个DirectChannel,并将这个id属性作为 Bean 名称。
在这种情况下,端点以 Bean 名id加上.adapter注册。
(如果 Bean 名是thing1,则端点注册为thing1.adapter。)
2 该端点接收Message实例的MessageChannel实例。
3 RedisConnectionFactory Bean.
的引用默认为redisConnectionFactory
4 用于执行基于队列的“推送”操作以发送 Redis 消息的 Redis 列表的名称。
此属性与queue-expression互斥。
5 一个 spelExpression来确定 Redis 列表的名称。
它在运行时使用传入的Message作为#root变量。
此属性与queue互斥。
6 aRedisSerializer Bean 引用。
它默认为 aJdkSerializationRedisSerializer
但是,对于String有效载荷,如果不提供serializer引用,则使用StringRedisSerializer
7 指定这个端点应该只向 Redis 队列发送有效负载还是整个Message
它默认为true
8 指定此端点是否应该使用“左推”(当true时)或“右推”(当false时)将消息写入 Redis 列表,
如果true,当与默认的 Redis 队列入站通道适配器一起使用时,Redis 列表充当FIFO队列。
将其设置为false以便与软件一起使用从列表中读取带有“left pop”或实现堆栈式消息顺序。
它默认为true
自版本 4.3 起。

# Redis 应用程序事件

Spring Integration3.0 以来,Redis 模块提供了IntegrationEvent的实现方式,这反过来是org.springframework.context.ApplicationEventRedisExceptionEvent封装了来自 Redis 操作的异常(端点是事件的“源”)。例如,<int-redis:queue-inbound-channel-adapter/>在捕获来自BoundListOperations.rightPop操作的异常后会发出这些事件。例外情况可以是任何通用的org.springframework.data.redis.RedisSystemExceptionorg.springframework.data.redis.RedisConnectionFailureException。用<int-event:inbound-channel-adapter/>处理这些事件对于确定后台 Redis 任务的问题和采取管理操作是有用的。

# Redis 消息存储

正如Enterprise 整合模式一书中所描述的,消息存储 (opens new window)允许你持久化消息。在处理具有缓冲消息能力的组件(聚合器、重排序程序和其他组件)时,当可靠性受到关注时,这可能是有用的。在 Spring 集成中,MessageStore策略还为索赔检查 (opens new window)模式提供了基础,这也在 EIP 中进行了描述。

Spring Integration 的 Redis 模块提供RedisMessageStore。下面的示例展示了如何在聚合器中使用它:

<bean id="redisMessageStore" class="o.s.i.redis.store.RedisMessageStore">
    <constructor-arg ref="redisConnectionFactory"/>
</bean>

<int:aggregator input-channel="inputChannel" output-channel="outputChannel"
         message-store="redisMessageStore"/>

前面的示例是 Bean 配置,它需要一个RedisConnectionFactory作为构造函数参数。

默认情况下,RedisMessageStore使用 Java 序列化来序列化消息。但是,如果你希望使用不同的序列化技术(例如 JSON),则可以通过设置RedisMessageStorevalueSerializer属性来提供自己的序列化器。

从版本 4.3.10 开始,该框架分别为Message实例和MessageHeaders实例——MessageJacksonDeserializerMessageHeadersJacksonSerializer实例提供了 Jackson 序列化器和反序列化器实现。它们必须配置SimpleModule``ObjectMapper选项。此外,你应该在ObjectMapper上设置enableDefaultTyping,以便为每个序列化的复杂对象添加类型信息(如果你信任源)。然后在反序列化过程中使用该类型信息。该框架提供了一种名为JacksonJsonUtils.messagingAwareMapper()的实用方法,该方法已经提供了前面提到的所有属性和序列化器。此实用程序方法带有trustedPackages参数,用于限制用于反序列化的 Java 包,以避免安全漏洞。默认受信任的包:java.utiljava.langorg.springframework.messaging.supportorg.springframework.integration.supportorg.springframework.integration.messageorg.springframework.integration.store。要在RedisMessageStore中管理 JSON 序列化,你必须以类似于以下示例的方式对其进行配置:

RedisMessageStore store = new RedisMessageStore(redisConnectionFactory);
ObjectMapper mapper = JacksonJsonUtils.messagingAwareMapper();
RedisSerializer<Object> serializer = new GenericJackson2JsonRedisSerializer(mapper);
store.setValueSerializer(serializer);

从版本 4.3.12 开始,RedisMessageStore支持prefix选项,以允许区分相同 Redis 服务器上的存储实例。

# Redis 频道消息存储

RedisMessageStore显示在前面将每个组保持为单个键(组 ID)下的值。虽然你可以使用它来支持用于持久性的QueueChannel,但为此目的提供了专门的RedisChannelMessageStore(自版本 4.0 以来)。此存储对每个通道使用LIST,在发送消息时使用LPUSH,在接收消息时使用RPOP。默认情况下,此存储还使用 JDK 序列化,但你可以修改值序列化器,如前面描述的

我们建议使用此存储备份通道,而不是使用一般的RedisMessageStore。下面的示例定义了一个 Redis 消息存储,并在带有队列的通道中使用它:

<bean id="redisMessageStore" class="o.s.i.redis.store.RedisChannelMessageStore">
	<constructor-arg ref="redisConnectionFactory"/>
</bean>

<int:channel id="somePersistentQueueChannel">
    <int:queue message-store="redisMessageStore"/>
<int:channel>

用于存储数据的键的形式为:<storeBeanName>:<channelId>(在前面的示例中,redisMessageStore:somePersistentQueueChannel)。

此外,还提供了一个子类RedisChannelPriorityMessageStore。当你使用QueueChannel时,将以优先顺序接收消息。它使用标准的IntegrationMessageHeaderAccessor.PRIORITY标头并支持优先值(0 - 9)。具有其他优先级的消息(以及不具有优先级的消息)将按照 FIFO 顺序在具有优先级的消息之后检索。

这些存储仅实现BasicMessageGroupStore,而不实现MessageGroupStore
它们只能用于诸如支持QueueChannel的情况。

# Redis 元数据存储

Spring Integration3.0 引入了一种新的基于 Redis 的[MetadataStore](https://DOCS. Spring.io/ Spring-integration/DOCS/latest-ga/api/org/springframework/integration/metadata/metadatastore.html)(参见元数据存储)实现。你可以使用RedisMetadataStore在应用程序重新启动时维护MetadataStore的状态。你可以将这个新的MetadataStore实现与适配器一起使用,例如:

要指示这些适配器使用新的RedisMetadataStore,请声明名为metadataStore的 Spring Bean。提要入站通道适配器和提要入站通道适配器都会自动拾取并使用声明的RedisMetadataStore。下面的示例展示了如何声明这样的 Bean:

<bean name="metadataStore" class="o.s.i.redis.store.metadata.RedisMetadataStore">
    <constructor-arg name="connectionFactory" ref="redisConnectionFactory"/>
</bean>

RedisMetadataStore由[RedisProperties](https://DOCS. Spring.io/ Spring-data/data-redis/DOCS/current/api/org/springframework/data/redis/support/collections/redisproperties.html)支持。与它的交互使用[BoundHashOperations](https://DOCS. Spring.io/ Spring-data/data-redis/DOCS/current/api/org/springframework/data/redis/core/boundhashoperations.html),而这反过来又要求整个key存储使用key。在MetadataStore的情况下,这个key扮演一个区域的角色,这在分布式环境中很有用,当几个应用程序使用相同的 Redis 服务器时。默认情况下,这个key的值是MetaData

从版本 4.0 开始,这个存储实现了ConcurrentMetadataStore,让它在多个应用程序实例之间可靠地共享,在这些应用程序实例中,只允许一个实例存储或修改键值。

你不能将RedisMetadataStore.replace()(例如,在AbstractPersistentAcceptOnceFileListFilter中)与 Redis 集群一起使用,因为当前不支持原子性的WATCH命令。

# Redis 存储入站通道适配器

Redis 存储入站通道适配器是一个轮询使用者,它从 Redis 集合中读取数据,并将其作为Message有效负载发送。下面的示例展示了如何配置 Redis 存储入站通道适配器:

<int-redis:store-inbound-channel-adapter id="listAdapter"
    connection-factory="redisConnectionFactory"
    key="myCollection"
    channel="redisChannel"
    collection-type="LIST" >
    <int:poller fixed-rate="2000" max-messages-per-poll="10"/>
</int-redis:store-inbound-channel-adapter>

前面的示例展示了如何通过使用store-inbound-channel-adapter元素来配置 Redis 存储入站通道适配器,该元素为各种属性提供值,例如:

  • keykey-expression:所使用的集合的键的名称。

  • collection-type:此适配器支持的集合类型的枚举。支持的集合是LISTSETZSETPROPERTIESMAP

  • connection-factory:引用o.s.data.redis.connection.RedisConnectionFactory的实例。

  • redis-template:引用o.s.data.redis.core.RedisTemplate的实例。

  • 在所有其他入站适配器中通用的其他属性(例如“channel”)。

你不能同时设置redis-templateconnection-factory
默认情况下,适配器使用StringRedisTemplate
这将使用StringRedisSerializer实例来表示键、值、散列键和散列值。
如果你的 Redis 存储中包含使用其他技术进行序列化的对象,则必须提供一个RedisTemplate配置了适当的序列化器,例如,
,如果存储被写入使用一个 Redis 存储出站适配器,该适配器的extract-payload-elements设置为false,你必须提供一个RedisTemplate配置如下:

<br/><bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"><br/> <property name="connectionFactory" ref="redisConnectionFactory"/><br/> <property name="keySerializer"><br/> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/><br/> </property><br/> <property name="hashKeySerializer"><br/> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/><br/> </property><br/></bean><br/>
RedisTemplate``RedisTemplate``RedisTemplate使用String序列化器来表示键和散列键,并使用默认的 JDK 序列化器来表示值和散列值。

因为它有一个key的文本值,所以前面的示例是相对简单和静态的。有时,你可能需要根据某些条件在运行时更改键的值。要做到这一点,请使用key-expression代替,其中提供的表达式可以是任何有效的 SPEL 表达式。

此外,你可能希望对从 Redis 集合中读取的已成功处理的数据执行一些后处理。例如,你可能希望在其被处理后移动或删除该值。你可以通过使用 Spring Integration2.2 中添加的事务同步特性来实现这一点。下面的示例使用key-expression和事务同步:

<int-redis:store-inbound-channel-adapter id="zsetAdapterWithSingleScoreAndSynchronization"
        connection-factory="redisConnectionFactory"
        key-expression="'presidents'"
        channel="otherRedisChannel"
        auto-startup="false"
        collection-type="ZSET">
            <int:poller fixed-rate="1000" max-messages-per-poll="2">
                <int:transactional synchronization-factory="syncFactory"/>
            </int:poller>
</int-redis:store-inbound-channel-adapter>

<int:transaction-synchronization-factory id="syncFactory">
	<int:after-commit expression="payload.removeByScore(18, 18)"/>
</int:transaction-synchronization-factory>

<bean id="transactionManager" class="o.s.i.transaction.PseudoTransactionManager"/>

你可以使用transactional元素将你的 Poller 声明为事务性的。这个元素可以引用一个真正的事务管理器(例如,如果流的其他部分调用 JDBC)。如果没有“真正的”事务,则可以使用o.s.i.transaction.PseudoTransactionManager,这是 Spring 的PlatformTransactionManager的实现,并且在没有实际事务的情况下启用 Redis 适配器的事务同步功能。

这并不使 Redis 活动本身具有事务性。
它允许在成功之前或之后(提交)或失败之后(回滚)进行操作的同步。

一旦你的 poller 是事务性的,你就可以在transactional元素上设置o.s.i.transaction.TransactionSynchronizationFactory的实例。TransactionSynchronizationFactory创建TransactionSynchronization的实例。为了你的方便,我们公开了一个默认的基于 SPEL 的TransactionSynchronizationFactory,它允许你配置 SPEL 表达式,其执行将与事务协调(同步)。支持用于 before-commit、after-commit 和 after-rollback 的表达式,以及发送计算结果(如果有的话)的通道(每种事件的一个)。对于每个子元素,你可以指定expressionchannel属性。如果只存在channel属性,则将接收到的消息作为特定同步场景的一部分发送到那里。如果只存在expression属性,并且表达式的结果是非空值,则生成一条结果为有效负载的消息,并将其发送到默认通道(NullChannel),并显示在日志中(在DEBUG级别)。如果你希望评估结果转到特定的通道,请添加channel属性。如果表达式的结果是空的或无效的,则不会生成任何消息。

有关事务同步的更多信息,请参见事务同步

# 恢复存储出站通道适配器

Redisstore 出站通道适配器允许你将消息有效负载写入 Redis 集合,如下例所示:

<int-redis:store-outbound-channel-adapter id="redisListAdapter"
          collection-type="LIST"
          channel="requestChannel"
          key="myCollection" />

前面的配置 a Redis 通过使用store-inbound-channel-adapter元素存储出站通道适配器。它为各种属性提供了值,例如:

  • keykey-expression:所使用的集合的键的名称。

  • extract-payload-elements:如果设置为true(默认值)并且有效负载是“多值”对象的实例(即CollectionMap),则通过使用“addall”和“putall”语义来存储它。否则,如果设置为false,则无论其类型如何,有效负载都将存储为单个条目。如果有效负载不是“多值”对象的实例,则忽略此属性的值,并且有效负载始终存储为单个条目。

  • collection-type:此适配器支持的Collection类型的枚举。支持的集合是LISTSETZSETPROPERTIESMAP

  • map-key-expression:spel 表达式,返回所存储条目的键名。它仅在collection-typeMAPPROPERTIES且’extract-payload-elements’为 false 时才适用。

  • connection-factory:引用o.s.data.redis.connection.RedisConnectionFactory的实例。

  • redis-template:引用o.s.data.redis.core.RedisTemplate的实例。

  • 在所有其他入站适配器中通用的其他属性(例如“channel”)。

你不能同时设置redis-templateconnection-factory
默认情况下,适配器使用StringRedisTemplate
这将使用StringRedisSerializer实例表示键、值、散列键和散列值,
但是,如果extract-payload-elements设置为false,将使用RedisTemplateStringRedisSerializer实例表示键和散列键,JdkSerializationRedisSerializer实例表示值和散列值。
有了 JDK 序列化器,理解 Java 序列化用于所有值是很重要的,不管这个值实际上是不是集合。
如果你需要更多地控制值的序列化,请考虑提供你自己的RedisTemplate,而不是依赖这些默认值。

因为它具有key和其他属性的文字值,所以前面的示例是相对简单且静态的。有时,你可能需要在运行时根据某些条件动态更改这些值。要做到这一点,使用它们的-expression等价物(key-expressionmap-key-expression,以此类推),其中提供的表达式可以是任何有效的 SPEL 表达式。

# Redis 出站命令网关

Spring Integration4.0 引入了 Redis 命令网关,以允许你通过使用通用的RedisConnection#execute方法执行任何标准的 Redis 命令。下面的清单显示了 Redis 出站网关的可用属性:

<int-redis:outbound-gateway
        request-channel=""  (1)
        reply-channel=""  (2)
        requires-reply=""  (3)
        reply-timeout=""  (4)
        connection-factory=""  (5)
        redis-template=""  (6)
        arguments-serializer=""  (7)
        command-expression=""  (8)
        argument-expressions=""  (9)
        use-command-variable=""  (10)
        arguments-strategy="" /> (11)
1 该端点接收Message实例的MessageChannel实例。
2 MessageChannel中,此端点发送回复Message实例。
3 指定此出站网关是否必须返回非空值。
它默认为true
ReplyRequiredException当 Redis 返回null值时抛出。
4 等待回复消息发送的超时(以毫秒为单位)。
它通常应用于基于队列的有限回复通道。
5 RedisConnectionFactory Bean 的引用。
它默认为redisConnectionFactory
它与’redis-template’属性互斥。
6 RedisTemplate Bean.
的引用与“connection-factory”属性互斥。
7 org.springframework.data.redis.serializer.RedisSerializer实例的引用。
它用于在必要时将每个命令参数序列化为字节[]。
8 返回命令键的 SPEL 表达式。
它默认为redis_command消息头。
它不能求值为null
9 用逗号分隔的 SPEL 表达式,作为命令参数计算。
arguments-strategy属性互斥。
如果两个属性都不提供,则payload用作命令参数。
参数表达式可以计算为“null”,以支持数量可变的参数。
10 一个boolean标志,用于指定当argument-expressions被配置时,在o.s.i.redis.outbound.ExpressionArgumentsStrategy的表达式求值上下文中,是否将已计算的 Redis 命令字符串作为#cmd变量提供。
否则将忽略此属性。
11 引用o.s.i.redis.outbound.ArgumentsStrategy的实例。
它与argument-expressions属性是互斥的。
如果你不提供这两个属性,payload将用作命令参数。

你可以使用<int-redis:outbound-gateway>作为公共组件来执行任何所需的 Redis 操作。下面的示例展示了如何从 Redis 原子序数获得递增的值:

<int-redis:outbound-gateway request-channel="requestChannel"
    reply-channel="replyChannel"
    command-expression="'INCR'"/>

Message有效载荷的名称应该是redisCounter,这可以由org.springframework.data.redis.support.atomic.RedisAtomicInteger Bean 定义提供。

RedisConnection#execute方法有一个通用的Object作为其返回类型。实际结果取决于命令类型。例如,MGET返回一个List<byte[]>。有关命令、其参数和结果类型的更多信息,请参见Redis 规范 (opens new window)

# Redis 队列出站网关

Spring 集成引入了 Redis 队列出站网关来执行请求和应答场景。它将一个对话UUID推送到所提供的queue,将带有UUID作为其键的值推送到一个 Redis 列表,并等待来自一个键为UUID' plus '.reply的 Redis 列表的回复。每个交互使用不同的 UUID。下面的清单显示了 Redis 出站网关的可用属性:

<int-redis:queue-outbound-gateway
        request-channel=""  (1)
        reply-channel=""  (2)
        requires-reply=""  (3)
        reply-timeout=""  (4)
        connection-factory=""  (5)
        queue=""  (6)
        order=""  (7)
        serializer=""  (8)
        extract-payload=""/>  (9)
1 该端点接收Message实例的MessageChannel实例。
2 MessageChannel中,此端点发送回复Message实例。
3 指定此出站网关是否必须返回非空值。
此值默认为false
否则,当 Redis 返回ReplyRequiredException值时,将抛出一个ReplyRequiredException值。
4 等待回复消息发送的超时(以毫秒为单位)。
它通常应用于基于队列的有限回复通道。
5 RedisConnectionFactory Bean 的引用。
它默认为redisConnectionFactory
它与’redis-template’属性互斥。
6 出站网关向其发送对话UUID的 Redis 列表的名称。
7 当注册了多个网关时,此出站网关的顺序。
8 RedisSerializer Bean 引用。
它可以是一个空字符串,这意味着“没有序列化器”。
在这种情况下,来自入站 Redis 消息的 RAWbyte[]被发送到channel作为Message有效载荷。
默认情况下,它是JdkSerializationRedisSerializer
9 指定此端点是否期望来自 Redis 队列的数据包含整个Message实例。
如果将此属性设置为true,则serializer不能是空字符串,因为消息需要某种形式的反序列化(默认情况下是 JDK 序列化)。

# Redis 队列入站网关

Spring 集成 4.1 引入了 REDIS 队列入站网关来执行请求和应答场景。它从提供的queue中弹出一个对话UUID,从 Redis 列表中弹出以UUID作为其键的值,并将该回复推到 Redis 列表中,其键为UUID加上.reply。下面的清单显示了 Redis 队列入站网关的可用属性:

<int-redis:queue-inbound-gateway
        request-channel=""  (1)
        reply-channel=""  (2)
        executor=""  (3)
        reply-timeout=""  (4)
        connection-factory=""  (5)
        queue=""  (6)
        order=""  (7)
        serializer=""  (8)
        receive-timeout=""  (9)
        expect-message=""  (10)
        recovery-interval=""/>  (11)
1 这个端点发送从 Redis 数据创建的MessageChannel实例的Message实例。
2 从该端点等待回复的MessageChannel实例。
可选-replyChannel标头仍在使用。
3 对 Spring TaskExecutor(或标准 JDKExecutor) Bean 的引用。
它用于底层监听任务。
它默认为SimpleAsyncTaskExecutor
4 等待回复消息发送的超时(以毫秒为单位)。
它通常应用于基于队列的有限回复通道。
5 RedisConnectionFactory Bean 的引用。
它默认为redisConnectionFactory
它与’redis-template’属性互斥。
6 对话UUID的 Redis 列表的名称。
7 当注册了多个网关时,此入站网关的顺序。
8 RedisSerializer Bean 引用。
它可以是一个空字符串,这意味着“没有序列化器”。
在这种情况下,来自入站 Redis 消息的 RAWbyte[]被发送到作为<r="500"/>有效负载。
它默认为JdkSerializationRedisSerializer。(请注意,在版本 4.3 之前的版本中,默认情况下是StringRedisSerializer
要恢复该行为,请提供对StringRedisSerializer的引用)。
9 等待接收消息后的超时(以毫秒为单位)。
它通常应用于基于队列的有限请求通道。
10 指定此端点是否期望来自 Redis 队列的数据包含整个Message实例。
如果将此属性设置为true,则serializer不能是空字符串,因为消息需要某种形式的反序列化(默认情况下是 JDK 序列化)。
11 侦听器任务在“右 POP”操作出现异常后应该休眠的时间(以毫秒为单位),然后再重新启动侦听器任务。
task-executor必须配置多个线程进行处理;否则,当RedisQueueMessageDrivenEndpoint在出现错误后试图重新启动侦听器任务时,可能会出现死锁,
errorChannel可以用来处理这些错误,以避免重新启动,但最好不要将你的应用程序暴露在可能的死锁情况下。
有关可能的TaskExecutor实现,请参见 Spring Framework参考手册 (opens new window)

# Redis 流出站通道适配器

Spring 集成 5.4 引入了反应性 Redis 流出站信道适配器,以将消息有效负载写入 Redis 流。出站通道适配器使用ReactiveStreamOperations.add(…​)向流添加Record。下面的示例展示了如何为 Redis 流出站通道适配器使用 Java 配置和服务类。

@Bean
@ServiceActivator(inputChannel = "messageChannel")
public ReactiveRedisStreamMessageHandler reactiveValidatorMessageHandler(
        ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
    ReactiveRedisStreamMessageHandler reactiveStreamMessageHandler =
        new ReactiveRedisStreamMessageHandler(reactiveRedisConnectionFactory, "myStreamKey"); (1)
    reactiveStreamMessageHandler.setSerializationContext(serializationContext); (2)
    reactiveStreamMessageHandler.setHashMapper(hashMapper); (3)
    reactiveStreamMessageHandler.setExtractPayload(true); (4)
    return reactiveStreamMessageHandler;
}
1 使用ReactiveRedisConnectionFactory和流名构造ReactiveRedisStreamMessageHandler的实例来添加记录。
另一个构造函数变体是基于 SPEL 表达式来根据请求消息计算流键。
2 设置一个RedisSerializationContext,用于在向流添加之前序列化一个记录键和值。
3 设置HashMapper,它提供 Java 类型和 Redis 散列/映射之间的契约。
4 如果“true”,通道适配器将从请求消息中提取有效负载,用于添加流记录。
或将整个消息用作一个值。
它默认为true

# Redis 流入站通道适配器

Spring 集成 5.4 引入了用于从 Redis 流读取消息的反应流入站通道适配器。入站通道适配器基于自动确认标志使用StreamReceiver.receive(…​)StreamReceiver.receiveAutoAck()从 Redis 流读取记录。下面的示例展示了如何为 Redis 流入站通道适配器使用 Java 配置。

@Bean
public ReactiveRedisStreamMessageProducer reactiveRedisStreamProducer(
       ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
ReactiveRedisStreamMessageProducer messageProducer =
            new ReactiveRedisStreamMessageProducer(reactiveRedisConnectionFactory, "myStreamKey"); (1)
    messageProducer.setStreamReceiverOptions( (2)
                StreamReceiver.StreamReceiverOptions.builder()
                      .pollTimeout(Duration.ofMillis(100))
                      .build());
    messageProducer.setAutoStartup(true); (3)
    messageProducer.setAutoAck(false); (4)
    messageProducer.setCreateConsumerGroup(true); (5)
    messageProducer.setConsumerGroup("my-group"); (6)
    messageProducer.setConsumerName("my-consumer"); (7)
    messageProducer.setOutputChannel(fromRedisStreamChannel); (8)
    messageProducer.setReadOffset(ReadOffset.latest()); (9)
    messageProducer.extractPayload(true); (10)
    return messageProducer;
}
1 构造ReactiveRedisStreamMessageProducer的实例,使用ReactiveRedisConnectionFactory和流键来读取记录。
2 aStreamReceiver.StreamReceiverOptions使用反应性基础设施来消耗 Redis 流。
3 一个SmartLifecycle属性,用于指定此端点是否应在应用程序上下文启动后自动启动。
它默认为true
如果falseRedisStreamMessageProducer应该手动启动messageProducer.start()
4 如果false,则接收到的消息不是自动确认的。
消息的确认将推迟到客户端消费消息。
它默认为true
5 如果true,将创建一个消费者组。
在创建消费者组时也将创建一个流(如果还不存在的话)。
消费者组跟踪消息传递并区分消费者组。
它默认为false
6 设置消费者组名称。
它默认为定义的 Bean 名称。
7 设置消费者名称。
将消息从组my-group中读取为my-consumer
8 从该端点向其发送消息的消息通道。
9 定义偏移量来读取消息。
它默认为ReadOffset.latest()
10 如果“true”,通道适配器将从Record提取有效载荷值。
否则将整个Record用作有效负载。
它默认为true

如果autoAck设置为false,则 Redis 流中的Record不会由 Redis 驱动程序自动确认,而是将IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK头添加到消息中,以产生一个SimpleAcknowledgment实例作为值。每当基于这样的记录为消息执行业务逻辑时,目标集成流负责调用其acknowledge()回调。即使在反序列化过程中发生异常并且errorChannel被配置时,也需要类似的逻辑。因此,目标错误处理程序必须决定 ACK 或 NACK 这样的失败消息。除了IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACKReactiveRedisStreamMessageProducer还将这些头填充到消息中,以生成:RedisHeaders.STREAM_KEYRedisHeaders.STREAM_MESSAGE_IDRedisHeaders.CONSUMER_GROUPRedisHeaders.CONSUMER

从版本 5.5 开始,你可以在ReactiveRedisStreamMessageProducer上显式地配置StreamReceiver.StreamReceiverOptionsBuilder选项,包括新引入的onErrorResume函数,如果出现反序列化错误时,Redis 流使用者应该继续轮询,则需要该函数。默认函数向错误通道(如果提供)发送一条消息,并对上面描述的失败消息进行可能的确认。所有这些StreamReceiver.StreamReceiverOptionsBuilder与外部提供的StreamReceiver.StreamReceiverOptions是互斥的。

# Redis 锁定注册表

Spring Integration4.0 引入了RedisLockRegistry。某些组件(例如,聚合器和重排序程序)使用从LockRegistry实例获得的锁,以确保一次只有一个线程操作组。DefaultLockRegistry在单个组件中执行此功能。现在,你可以在这些组件上配置一个外部锁注册中心。当你将它与共享的MessageGroupStore一起使用时,你可以使用RedisLockRegistry来跨多个应用程序实例提供此功能,使得一次只有一个实例可以操作组。

当一个本地线程释放一个锁时,另一个本地线程通常可以立即获得该锁。如果一个线程使用不同的注册中心实例释放了一个锁,那么获取该锁可能需要多达 100ms 的时间。

为了避免“挂起”锁(当服务器发生故障时),此注册中心中的锁在默认的 60 秒后过期,但是你可以在注册中心上配置该值。通常锁的时间要短得多。

由于密钥可能过期,试图解锁过期的锁将导致引发异常,
但是,受此锁保护的资源可能已遭到破坏,所以这样的异常应该被认为是严重的。
你应该将过期时间设置为足够大的值,以防止出现这种情况,但是将过期时间设置得足够低,以便在服务器故障后在合理的时间内恢复锁。

从版本 5.0 开始,RedisLockRegistry实现了ExpirableLockRegistry,它删除了上次获得的超过age的锁,并且这些锁目前没有被锁定。

使用 5.5.6 版本的字符串,RedisLockRegistry支持通过RedisLockRegistry.setCacheCapacity()RedisLockRegistry.locks中自动清理缓存以进行重新锁定。有关更多信息,请参见其 Javadocs。