原标题:Spring认证中国教育管理中心-Spring Data Redis框架教程一
参考文档的这一部分解释了 Spring Data Redis 提供的核心功能。它解释了键值模块的概念和语义以及各种商店命名空间的语法。有关键值存储、Spring 或 Spring Data 示例的介绍,请参阅学习 NoSQL 和键值存储。本文档仅涉及 Spring Data Redis 支持,并假设用户熟悉键值存储和 Spring 概念。
“ Redis 支持”介绍了 Redis 模块功能集。
“ Redis Repositories ”介绍了对Redis的存储库支持。
本文档是 Spring Data Redis (SDR) 支持的参考指南。
Spring Framework 是领先的全栈 Java/JEE 应用程序框架。它通过使用依赖注入、AOP 和可移植服务抽象提供了一个轻量级容器和一个非侵入式编程模型。
NoSQL存储系统提供了经典 RDBMS 的替代方案,以实现水平可扩展性和速度。在实现方面,键值存储代表 NoSQL 空间中最大(也是最古老)的成员之一。
Spring Data Redis (SDR) 框架通过 Spring 出色的基础架构支持消除了与存储交互所需的冗余任务和样板代码,从而可以轻松编写使用 Redis 键值存储的 Spring 应用程序。
Spring Data 支持的键值存储之一是Redis。引用Redis项目主页:
Redis 是一种高级键值存储。它与 memcached 类似,但数据集不是易失性的,值可以是字符串,就像在 memcached 中一样,但也可以是列表、集合和有序集合。所有这些数据类型都可以通过原子操作进行操作,以推送/弹出元素、添加/删除元素、执行服务器端并集、交集、集合之间的差异等。Redis 支持不同种类的排序能力。
Spring Data Redis 提供了从 Spring 应用程序轻松配置和访问 Redis 的功能。它提供了用于与商店交互的低级和高级抽象,使用户摆脱了对基础设施的担忧。
设置工作环境的一种简单方法是在STS 中创建一个基于 Spring 的项目。
首先,您需要设置一个正在运行的 Redis 服务器。
在 STS 中创建一个 Spring 项目:
存储库也可在此处浏览。
Spring Redis 需要 Redis 2.6 或更高版本,并且 Spring Data Redis 与Lettuce和Jedis集成,这两个流行的 Redis 开源 Java 库。
Redis 支持提供了几个组件。对于大多数任务,高级抽象和支持服务是最佳选择。请注意,您可以在任何时候在层之间移动。例如,您可以获得一个低级连接(甚至是本机库)来直接与 Redis 通信。
使用 Redis 和 Spring 时的首要任务之一是通过 IoC 容器连接到存储。为此,需要一个 Java 连接器(或绑定)。无论你选择的图书馆,你只需要使用一组春天Redis的数据的API(这在所有连接器一致的行为):在
org.springframework.data.redis.connection包及其RedisConnection并RedisConnectionFactory与工作和检索到Redis的活动连接接口。
RedisConnection为 Redis 通信提供核心构建块,因为它处理与 Redis 后端的通信。它还自动将底层连接库异常转换为 Spring 一致的 DAO 异常层次结构,以便您可以在不更改任何代码的情况下切换连接器,因为操作语义保持不变。
对于需要本机库 API 的极端情况,RedisConnection提供了一个专用方法 ( getNativeConnection),该方法返回用于通信的原始底层对象。
活动RedisConnection对象是通过RedisConnectionFactory. 此外,工厂充当
PersistenceExceptionTranslator对象,这意味着一旦声明,它们就可以让您进行透明的异常转换。例如,您可以通过使用@Repository注解和 AOP进行异常翻译。有关更多信息,请参阅Spring Framework 文档中的专用部分。
根据底层配置,工厂可以返回新连接或现有连接(当使用池或共享本机连接时)。
使用 a 的最简单方法RedisConnectionFactory是通过 IoC 容器配置适当的连接器并将其注入 using 类。
不幸的是,目前并非所有连接器都支持所有 Redis 功能。当在底层库不支持的 Connection API 上调用方法时,
UnsupportedOperationException会抛出an 。以下概述说明了各个 Redis 连接器支持的功能:
Lettuce是Spring Data Redis 通过包支持的基于Netty的开源连接器
org.springframework.data.redis.connection.lettuce。
将以下内容添加到 pom.xml 文件dependencies元素:
<dependencies> <!-- other dependency elements omitted --> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>6.1.5.RELEASE</version> </dependency> </dependencies>
以下示例显示了如何创建新的 Lettuce 连接工厂:
@Configuration class AppConfig { @Bean public LettuceConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379)); } }
还有一些特定于生菜的连接参数可以调整。默认情况下,LettuceConnection由 所创建的所有实例LettuceConnectionFactory为所有非阻塞和非事务性操作共享相同的线程安全本机连接。要每次使用专用连接,请设置shareNativeConnection为false。如果设置为 ,LettuceConnectionFactory也可以配置为使用 aLettucePool用于池化阻塞和事务连接或所有连接。
shareNativeConnectionfalse
Lettuce 与 Netty 的原生传输集成,让您可以使用 Unix 域套接字与 Redis 进行通信。确保包含与您的运行时环境匹配的适当本机传输依赖项。以下示例显示了如何为 Unix 域套接字创建生菜连接工厂/var/run/redis.sock:
@Configuration class AppConfig { @Bean public LettuceConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(new RedisSocketConfiguration("/var/run/redis.sock")); } }
Netty 目前支持用于操作系统原生传输的 epoll (Linux) 和 kqueue (BSD/macOS) 接口。
10.4.3.配置 Jedis 连接器
Jedis是 Spring Data Redis 模块通过
org.springframework.data.redis.connection.jedis包支持的社区驱动的连接器。
将以下内容添加到 pom.xml 文件dependencies元素:
<dependencies> <!-- other dependency elements omitted --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.7.0</version> </dependency> </dependencies>
Jedis 配置最简单的形式如下:
@Configuration class AppConfig { @Bean public JedisConnectionFactory redisConnectionFactory() { return new JedisConnectionFactory(); } }
但是,对于生产用途,您可能需要调整主机或密码等设置,如以下示例所示:
@Configuration class RedisConfiguration { @Bean public JedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("server", 6379); return new JedisConnectionFactory(config); } }
Redis 主/副本设置——没有自动故障转移(关于自动故障转移,请参阅:哨兵)——不仅允许数据安全地存储在更多节点上。它还允许通过使用Lettuce从副本读取数据,同时将写入推送到主服务器。您可以使用 设置要使用的读/写策略,
LettuceClientConfiguration如下例所示:
@Configuration class WriteToMasterReadFromReplicaConfiguration { @Bean public LettuceConnectionFactory redisConnectionFactory() { LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .readFrom(REPLICA_PREFERRED) .build(); RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379); return new LettuceConnectionFactory(serverConfig, clientConfig); } }
对于通过INFO命令报告非公共地址的环境(例如,在使用 AWS 时),请使用
RedisStaticMasterReplicaConfiguration代替RedisStandaloneConfiguration。请注意,RedisStaticMasterReplicaConfiguration由于缺少跨单个服务器的 Pub/Sub 消息传播,因此不支持 Pub/Sub。
对于处理高可用Redis,Spring Data Redis 已经支持Redis Sentinel,使用
RedisSentinelConfiguration,如下例所示:
/** * Jedis */ @Bean public RedisConnectionFactory jedisConnectionFactory() { RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster") .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380); return new JedisConnectionFactory(sentinelConfig); } /** * Lettuce */ @Bean public RedisConnectionFactory lettuceConnectionFactory() { RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster") .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380); return new LettuceConnectionFactory(sentinelConfig); }
RedisSentinelConfiguration也可以用 a 定义PropertySource,它允许您设置以下属性:
配置属性
spring.redis.sentinel.master: 主节点的名称。
spring.redis.sentinel.nodes: 逗号分隔的主机:端口对列表。
spring.redis.sentinel.password: 使用 Redis Sentinel 进行身份验证时应用的密码
有时,需要与其中一个哨兵直接交互。使用
RedisConnectionFactory.getSentinelConnection()或RedisConnection.getSentinelCommands()允许您访问配置的第一个活动 Sentinel。
大多数用户可能会使用RedisTemplate及其对应的包,
org.springframework.data.redis.core. 由于其丰富的功能集,模板实际上是 Redis 模块的中心类。该模板为 Redis 交互提供了高级抽象。虽然RedisConnection提供接受和返回二进制值(byte数组)的低级方法,但模板负责序列化和连接管理,使用户免于处理此类细节。
此外,模板提供操作视图(遵循 Redis 命令参考中的分组),提供丰富的通用接口,用于针对特定类型或特定键(通过KeyBound接口)工作,如下表所述:
配置后,模板是线程安全的,可以跨多个实例重复使用。
RedisTemplate大多数操作使用基于 Java 的序列化程序。这意味着模板写入或读取的任何对象都通过 Java 进行序列化和反序列化。您可以更改模板上的序列化机制,Redis 模块提供了多种实现,可在
org.springframework.data.redis.serializer包中使用。有关更多信息,请参阅序列化程序。您还可以将任何序列化程序设置为 null 并通过将enableDefaultSerializer属性设置为 RedisTemplate 与原始字节数组false。请注意,模板要求所有键都为非空。但是,只要底层序列化程序接受它们,值就可以为空。阅读每个序列化程序的 Javadoc 以获取更多信息。
对于需要某个模板视图的情况,将视图声明为依赖项并注入模板。容器自动执行转换,消除opsFor[X]调用,如以下示例所示:
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/> <!-- redis template definition --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/> ... </beans>
public class Example { // inject the actual template @Autowired private RedisTemplate<String, String> template; // inject the template as ListOperations @Resource(name="redisTemplate") private ListOperations<String, String> listOps; public void addLink(String userId, URL url) { listOps.leftPush(userId, url.toExternalForm()); } }
由于存储在 Redis 中的键和值很常见java.lang.String,因此 Redis 模块提供了RedisConnection和 的两个扩展RedisTemplate,分别是StringRedisConnection(及其
DefaultStringRedisConnection实现)和StringRedisTemplate作为密集字符串操作的便捷一站式解决方案。除了绑定到String键之外,模板和连接使用StringRedisSerializer底层,这意味着存储的键和值是人类可读的(假设在 Redis 和您的代码中使用相同的编码)。以下清单显示了一个示例:
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/> <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="jedisConnectionFactory"/> ... </beans>
public class Example { @Autowired private StringRedisTemplate redisTemplate; public void addLink(String userId, URL url) { redisTemplate.opsForList().leftPush(userId, url.toExternalForm()); } }
由于与其他Spring模板,RedisTemplate并StringRedisTemplate让你直接通过交谈Redis的RedisCallback界面。此功能可让您完全控制,因为它直接与RedisConnection. 请注意,回调接收使用StringRedisConnectiona 时的实例StringRedisTemplate。以下示例显示了如何使用该RedisCallback接口:
public void useCallback() { redisTemplate.execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection connection) throws DataAccessException { Long size = connection.dbSize(); // Can cast to StringRedisConnection if using a StringRedisTemplate ((StringRedisConnection)connection).set("key", "value"); } }); }
从框架的角度来看,Redis 中存储的数据只有字节。虽然 Redis 本身支持各种类型,但在大多数情况下,这些类型指的是数据的存储方式,而不是它所代表的内容。由用户决定是否将信息转换为字符串或任何其他对象。
在 Spring Data 中,用户(自定义)类型和原始数据(反之亦然)之间的转换在
org.springframework.data.redis.serializer包中由 Redis 处理。
这个包包含两种类型的序列化器,顾名思义,它们负责序列化过程:
这些变体之间的主要区别在于,RedisSerializer主要是byte[]在读者和作者使用ByteBuffer.
有多种实现可用(包括本文档中已经提到的两种):
但是,可以OxmSerializer通过 Spring OXM支持用于对象/XML 映射,
Jackson2JsonRedisSerializer或GenericJackson2JsonRedisSerializer用于以JSON格式存储数据。
请注意,存储格式不仅限于值。它可以用于键、值或散列,没有任何限制。
默认情况下,RedisCache并RedisTemplate配置为使用 Java 本机序列化。Java 本机序列化以允许运行由利用易受攻击的库和类注入未经验证的字节码的有效载荷引起的远程代码而闻名。操纵输入可能会导致在反序列化步骤期间在应用程序中运行不需要的代码。因此,不要在不受信任的环境中使用序列化。通常,我们强烈建议您改用任何其他消息格式(例如 JSON)。
如果您担心 Java 序列化导致的安全漏洞,请考虑核心 JVM 级别的通用序列化过滤器机制,该机制最初是为 JDK 9 开发的,但后来移植到 JDK 8、7 和 6:
过滤传入的序列化数据。
JEP 290。
OWASP:不可信数据的反序列化。
可以使用Redis 中的各种数据结构来存储数据。
Jackson2JsonRedisSerializer可以转换JSON格式的对象。理想情况下,可以使用普通键将 JSON 存储为值。您可以通过使用 Redis 哈希来实现更复杂的结构化对象映射。Spring Data Redis 提供了各种将数据映射到哈希的策略(取决于用例):
哈希映射器是映射对象到 aMap<K, V>和返回的转换器。HashMapper旨在与 Redis 哈希一起使用。
有多种实现可用:
以下示例显示了一种实现哈希映射的方法:
public class Person { String firstname; String lastname; // … } public class HashMapping { @Autowired HashOperations<String, byte[], byte[]> hashOperations; HashMapper<Object, byte[], byte[]> mapper = new ObjectHashMapper(); public void writeHash(String key, Person person) { Map<byte[], byte[]> mappedHash = mapper.toHash(person); hashOperations.putAll(key, mappedHash); } public Person loadHash(String key) { Map<byte[], byte[]> loadedHash = hashOperations.entries("key"); return (Person) mapper.fromHash(loadedHash); } }
Jackson2HashMapper使用FasterXML Jackson为域对象提供 Redis 哈希映射。 Jackson2HashMapper可以将顶级属性映射为哈希字段名称,并且可以选择将结构展平。简单类型映射到简单值。复杂类型(嵌套对象、集合、映射等)表示为嵌套 JSON。
展平为所有嵌套属性创建单独的哈希条目,并尽可能将复杂类型解析为简单类型。
考虑以下类及其包含的数据结构:
public class Person { String firstname; String lastname; Address address; Date date; LocalDateTime localDateTime; } public class Address { String city; String country; }
下表显示了前面类中的数据在法线映射中的显示方式:
扁平化要求所有属性名称不干扰 JSON 路径。使用展平时,不支持在映射键中或作为属性名称使用点或括号。生成的散列无法映射回对象。
java.util.Date并java.util.Calendar以毫秒表示。如果 JSR-310 日期/时间类型在类路径上,toString则将其序列化为其形式jackson-datatype-jsr310。
Spring Data 为 Redis 提供了专用的消息传递集成,在功能和命名上类似于 Spring Framework 中的 JMS 集成。
Redis 消息传递大致可以分为两个方面的功能:
这是通常称为发布/订阅(简称 Pub/Sub)的模式示例。所述RedisTemplate类用于消息生成。对于类似于 Java EE 的消息驱动 bean 样式的异步接收,Spring Data 提供了一个专用的消息侦听器容器,用于创建消息驱动的 POJO(MDP)以及用于同步接收的RedisConnection合约。
在
org.springframework.data.redis.connection和org.springframework.data.redis.listener软件包提供了对Redis的消息的核心功能。
要发布消息,您可以像其他操作一样使用低级RedisConnection或高级RedisTemplate. 两个实体都提供了publish接受消息和目标通道作为参数的方法。虽然RedisConnection需要原始数据(字节数组),但RedisTemplate允许将任意对象作为消息传入,如下例所示:
// send message through connection RedisConnection con = ... byte[] msg = ... byte[] channel = ... con.publish(msg, channel); // send message through RedisTemplate RedisTemplate template = ... template.convertAndSend("hello!", "world");
在接收端,可以通过直接命名或使用模式匹配来订阅一个或多个频道。后一种方法非常有用,因为它不仅允许使用一个命令创建多个订阅,而且还可以侦听订阅时尚未创建的频道(只要它们匹配模式)。
在底层,RedisConnection提供subscribe和pSubscribe方法,分别映射 Redis 命令以按频道或按模式订阅。请注意,可以使用多个通道或模式作为参数。要更改连接的订阅或查询它是否正在侦听,RedisConnection提供getSubscription和isSubscribed方法。
Spring Data Redis 中的订阅命令被阻塞。也就是说,在连接上调用 subscribe 会导致当前线程在开始等待消息时阻塞。只有在取消订阅时才会释放线程,这发生在另一个线程调用unsubscribe或pUnsubscribe在同一连接上。有关此问题的解决方案,请参阅“消息侦听器容器”(本文档的后面部分)。
如前所述,一旦订阅,连接就会开始等待消息。仅允许添加新订阅、修改现有订阅和取消现有订阅的命令。调用比其他任何东西subscribe,pSubscribe,unsubscribe,或pUnsubscribe抛出异常。
为了订阅消息,需要实现MessageListener回调。每次有新消息到达时,都会调用回调并通过该onMessage方法运行用户代码。该接口不仅可以访问实际消息,还可以访问通过它接收到的通道以及订阅用于匹配通道的模式(如果有)。此信息使被调用者不仅可以通过内容而且还可以检查其他详细信息来区分各种消息。
由于其阻塞性质,低级订阅没有吸引力,因为它需要对每个监听器进行连接和线程管理。为了缓解这个问题,Spring Data 提供了
RedisMessageListenerContainer,它完成了所有繁重的工作。如果您熟悉 EJB 和 JMS,您应该会发现这些概念很熟悉,因为它被设计为尽可能接近 Spring Framework 及其消息驱动的 POJO (MDP) 中的支持。
RedisMessageListenerContainer充当消息侦听器容器。它用于从 Redis 通道接收消息并驱动MessageListener注入其中的实例。侦听器容器负责消息接收的所有线程并分派到侦听器中进行处理。消息侦听器容器是 MDP 和消息提供者之间的中介,负责注册接收消息、资源获取和释放、异常转换等。这让您作为应用程序开发人员可以编写与接收消息(并对其作出反应)相关的(可能很复杂)业务逻辑,并将样板 Redis 基础设施问题委托给框架。
`MessageListener` 还可以实现 `SubscriptionListener` 以在订阅/取消订阅确认时接收通知。同步调用时,侦听订阅通知很有用。
此外,为了最大限度地减少应用程序占用空间,
RedisMessageListenerContainer即使多个侦听器不共享订阅,也可以让它们共享一个连接和一个线程。因此,无论应用程序跟踪多少个侦听器或通道,运行时成本在其整个生命周期中都保持不变。此外,容器允许运行时配置更改,以便您可以在应用程序运行时添加或删除侦听器,而无需重新启动。此外,容器使用惰性订阅方法,RedisConnection仅在需要时使用。如果所有侦听器都取消订阅,则自动执行清理,并释放线程。
为了帮助处理消息的异步特性,容器需要一个
java.util.concurrent.Executor(或 Spring 的TaskExecutor)来分派消息。根据负载、侦听器的数量或运行时环境,您应该更改或调整执行程序以更好地满足您的需求。特别是在托管环境(例如应用服务器)中,强烈建议选择一个合适的TaskExecutor来利用其运行时。
本MessageListenerAdapter类是Spring的异步支持消息的最后一个组件。简而言之,它允许您将几乎任何类公开为 MDP(尽管有一些限制)。
考虑以下接口定义:
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); }
注意,虽然接口没有扩展MessageListener接口,但通过使用MessageListenerAdapter类,它仍然可以用作MDP 。还要注意如何使用各种消息处理方法是根据强类型的内容不同的Message类型,他们可以接收和处理。此外,消息发送到的通道或模式可以作为 type 的第二个参数传递给方法String:
public class DefaultMessageDelegate implements MessageDelegate { // implementation elided for clarity... }
注意上面的MessageDelegate接口实现(上面的DefaultMessageDelegate类)完全没有Redis 依赖。它确实是一个 POJO,我们使用以下配置将其制成 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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/redis https://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>
侦听器主题可以是频道(例如,topic=“chatroom”)或模式(例如,topic="*room")
上面的例子使用Redis命名空间来声明消息监听容器并自动将POJO注册为监听器。完整的 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>
每次接收到消息时,适配器都会自动透明地执行(使用配置的RedisSerializer)低级格式和所需对象类型之间的转换。任何由方法调用引起的异常都会被容器捕获并处理(默认情况下,异常会被记录)。