Spring Data Redis
©2011-2019 原作者。
Note
本文档的副本可以供您自己使用,也可以分发给其他人,但前提是您不对此类副本收取任何费用,并且还应确保每份副本均包含本版权声明(无论是印刷版本还是电子版本)。
目录
-
- [4\.1\.2\. Learning NoSQL and Key Value Stores](#get-started%3Afirst-steps%3Anosql) - [4\.1\.3\. Trying out the Samples](#get-started%3Afirst-steps%3Asamples)
-
- [4\.2\.2\. Professional Support](#get-started%3Ahelp%3Aprofessional)
-
5 .3.1. RedisConnection 和 RedisConnectionFactory
- [5\.3\.2\. Configuring the Jedis Connector](#redis%3Aconnectors%3Ajedis) - [5\.3\.3\. Configuring JRedis connector \(Deprecated since 1\.7\)](#redis%3Aconnectors%3Ajredis) - [5\.3\.4\. Configuring SRP connector \(Deprecated since 1\.7\)](#redis%3Aconnectors%3Asrp) - [5\.3\.5\. Configuring the Lettuce connector](#redis%3Aconnectors%3Alettuce)
-
- [5\.8\.2\. Jackson2HashMapper](#redis.hashmappers.jackson2)
-
- [5\.9\.2\. Subscribing \(Receiving Messages\)](#redis%3Apubsub%3Asubscribe)
-
- [7\.4\.2\. Geospatial Index](#redis.repositories.indexes.geospatial)
-
- [7\.11\.2\. Replace existing](#_replace_existing) - [7\.11\.3\. Save Geo Data](#_save_geo_data) - [7\.11\.4\. Find using simple index](#_find_using_simple_index) - [7\.11\.5\. Find using Geo Index](#_find_using_geo_index)
Preface
Spring Data Redis 项目通过使用键值样式数据存储将 Spring 的核心概念应用于解决方案的开发。我们提供“模板”作为用于发送和接收消息的高级抽象。您可能会注意到与 Spring Framework 中的 JDBC 支持相似。
1.新功能
本节简要介绍了最新版本中的新内容和值得注意的内容。
1.1. Spring Data Redis 1.8 的新功能
-
升级到 Jedis 2.9.
-
升级到
Lettuce
4.2(注意:Lettuce 4.2 需要 Java 8)。 -
支持 Redis GEO命令。
-
使用 Spring Data Repository 抽象支持地理空间索引(请参阅Geospatial Index)。
-
基于
MappingRedisConverter
的HashMapper
实现(请参见Hash mapping)。 -
在存储库中支持
PartialUpdate
(请参阅持续部分更新)。 -
SSL 支持与 Redis 集群的连接。
-
使用 Jedis 时支持
client name
至ConnectionFactory
。
1.2. Spring Data Redis 1.7 的新功能
-
支持RedisCluster。
-
支持 Spring Data Repository 抽象(请参阅Redis Repositories)。
1.3. Spring Data Redis 1.6 的新功能
-
Lettuce
Redis 驱动程序从wg/lettuce切换到mp911de/lettuce。 -
支持
ZRANGEBYLEX
。 -
ZSET
的增强范围操作,包括+inf
/-inf
。 -
RedisCache
中的性能改进,现在可以更早地释放连接。 -
通用 Jackson2
RedisSerializer
利用了 Jackson 的多态反序列化。
1.4. Spring Data Redis 1.5 的新功能
-
添加对 Redis HyperLogLog 命令的支持:
PFADD
,PFCOUNT
和PFMERGE
。 -
可配置的
JavaType
查找基于 Jackson 的RedisSerializers
。 -
基于
PropertySource
的配置,用于连接到 Redis Sentinel(请参阅:Redis 前哨支持)。
Introduction
本文档是 Spring Data Redis(SDR)支持的参考指南。它解释了键-值模块的概念和语义以及各种 Store 名称空间的语法。
有关键值存储,Spring 或 Spring Data 示例的介绍,请参见Getting Started。本文档仅涉及 Spring Data Redis 支持,并假定用户熟悉键值存储和 Spring 概念。
2.为什么使用 Spring Data Redis?
Spring 框架是领先的全栈 Java/JEE 应用程序框架。它提供了一个轻量级的容器和一个非侵入性编程模型,该模型通过使用依赖项注入,AOP 和可移植服务抽象来实现。
NoSQL存储系统提供了传统 RDBMS 的替代产品,以实现水平可伸缩性和速度。在实现方面,键值存储代表 NoSQL 空间中最大(和最旧)的成员之一。
Spring Data Redis(SDR)框架通过 Spring 出色的基础架构支持消除了与存储库交互所需的冗余任务和样板代码,从而简化了编写使用 Redis 键值存储库的 Spring 应用程序的过程。
3. Requirements
Spring Data Redis 2.x 二进制文件要求 JDK 级别 8.0 和更高以及Spring Framework 4.3.22.RELEASE 和更高。
就键值存储而言,需要Redis 2.6.x 或更高。 Spring Data Redis 当前已针对最新的 4.0 版本进行了测试。
4.入门
本部分提供了易于使用的指南,以帮助您开始使用 Spring Data Redis 模块。
4.1. 第一步
如为什么选择 Spring Data Redis?中所述,Spring Data Redis(SDR)在 Spring 框架和 Redis 键值存储之间提供集成。因此,您应该熟悉这两个框架。在整个 SDR 文档中,每个部分都提供了指向相关资源的链接。但是,在阅读本指南之前,您应该熟悉这些主题。
4.1.1. 学习之春
Spring Data 使用 Spring 框架的core功能,例如IoC容器,resource抽象和AOP基础结构。虽然了解 Spring API 并不重要,但了解它们背后的概念很重要。至少,IoC 背后的想法应该很熟悉。话虽这么说,您对 Spring 的了解越多,就能更快地获取 Spring Data Redis。除了 Spring Framework 的全面文档之外,还有很多有关此问题的文章,博客条目和书籍。Spring 指南home page提供了一个很好的起点。通常,这应该是想要尝试使用 Spring Data Redis 的开发人员的起点。
4.1.2. 学习 NoSQL 和键值存储
NoSQLStore 席卷了存储世界。它是一个广阔的领域,具有许多解决方案,术语和模式(更糟糕的是,即使术语本身也具有多个meanings)。尽管某些原则很普遍,但一定程度上要熟悉 SDR 支持的 Store,这一点至关重要。熟悉这些解决方案的最佳方法是阅读其文档并遵循其示例。通常不需要花费五到十分钟的时间来完成这些步骤,而且,如果您来自仅 RDMBS 的背景,那么很多时候这些练习可能会让您大开眼界。
4.1.3. 试用 samples
您可以在专用的 Spring 数据示例存储库http://github.com/spring-projects/spring-data-keyvalue-examples处找到键值存储的各种示例。对于 Spring Data Redis,您应该特别注意retwisj
示例,该示例是在 Redis 之上构建的 Twitter 克隆,可以在本地运行或部署到云中。有关更多信息,请参见其documentation,以下博客entry。
4.2. 需要帮忙?
如果您遇到问题或只是在寻求建议,请使用以下链接之一:
4.2.1. 社区支持
Stack Overflow上的 Spring Data 标签是一个留言板,供所有 Spring Data(不仅仅是 Redis)用户共享信息并互相帮助。请注意,注册仅 需要 才能发布。
4.2.2. 专业支持
Spring Data 和 Spring 背后的公司Pivotal Software,Inc.可提供专业的,源码支持的响应时间。
4.3. 后续 Developing
有关 Spring Data 源代码存储库,每夜构建和快照工件的信息,请参见 Spring Data 主页page。
您可以通过spring-data或spring-data-redis在 Stack Overflow 上与开发人员进行交互,从而使 Spring Data 最好地满足 Spring 社区的需求。
如果您遇到错误或要提出改进建议(包括本文档中的内容),请在 Spring Data 问题tracker上创建故障单。
要了解 Spring 生态系统中的最新新闻和公告,请订阅 Spring Community Portal。
最后,您可以在 Twitter 上关注 Spring blog或项目团队(Thomas和Christoph)。
Reference Documentation
Document structure
参考文档的这一部分说明了 Spring Data Redis 提供的核心功能。
Redis support引入了 Redis 模块功能集。
5.支持 Redis
Spring Data 支持的键值存储之一是Redis。引用 Redis 项目主页:
Note
Redis 是高级键值存储。它类似于 memcached,但数据集不是易失的,值可以是字符串,就像在 memcached 中一样,也可以是列表,集合和有序集合。所有这些数据类型都可以通过原子操作进行操作,以推入/弹出元素,添加/删除元素,执行服务器端联合,交集,集合之间的差异等。 Redis 支持不同种类的排序功能。
Spring Data Redis 提供了简单的配置,并可以从 Spring 应用程序访问 Redis。它提供了与 Store 交互的低层和高层抽象,使用户摆脱了基础设施的困扰。
5.1. Redis 需求
Spring Data Redis 需要 Redis 2.6 或更高版本以及 Java SE 6.0 或更高版本。在语言绑定(或连接器)方面,Spring Redis 与Jedis,JRedis(自 1.7 版弃用),SRP(自 1.7 版弃用)和Lettuce集成,这是 Redis 的四个流行开源 Java 库。如果您知道我们应与之集成的任何其他连接器,请向我们发送反馈。
5.2. Redis 支持高级视图
Redis 支持提供了几个组件。对于大多数任务,高级抽象和支持服务是最佳选择。请注意,您可以随时在图层之间移动。例如,您可以获得一个低级连接(甚至本机库)以直接与 Redis 通信。
5.3. 连接到 Redis
使用 Redis 和 Spring 时的首要任务之一是通过 IoC 容器连接到 Store。为此,需要 Java 连接器(或绑定)。无论选择哪种库,您都只需要使用一组 Spring Data Redis API(在所有连接器上行为均一):org.springframework.data.redis.connection
包及其RedisConnection
和RedisConnectionFactory
接口即可使用和检索与 Redis 的活动连接。
5.3.1. RedisConnection 和 RedisConnectionFactory
RedisConnection
提供了 Redis 通信的核心构建块,因为它处理与 Redis 后端的通信。它还会自动将基础连接库异常转换为 Spring 的一致 DAO 异常hierarchy,以便您可以在不更改任何代码的情况下切换连接器,因为操作语义保持不变。
Note
对于需要本机库 API 的特殊情况,RedisConnection
提供了一种专用方法(getNativeConnection
),该方法返回用于通信的原始基础对象。
有效的RedisConnection
对象是通过RedisConnectionFactory
创建的。另外,工厂充当PersistenceExceptionTranslator
对象,这意味着一旦声明,它们就可以让您进行透明的异常转换。例如,您可以使用@Repository
注解和 AOP 进行异常转换。有关更多信息,请参见 Spring Framework 文档中专用的section。
Note
根据基础配置,工厂可以返回新连接或现有连接(使用池或共享本机连接时)。
使用RedisConnectionFactory
的最简单方法是通过 IoC 容器配置适当的连接器,并将其注入 using 类。
Tip
不幸的是,当前,并非所有连接器都支持所有 Redis 功能。在基础库不支持的 Connection API 上调用方法时,将引发UnsupportedOperationException
。
5.3.2. 配置 Jedis 连接器
Jedis是 Spring Data Redis 模块通过org.springframework.data.redis.connection.jedis
软件包支持的连接器之一。 Jedis 配置以最简单的形式如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Jedis ConnectionFactory -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"/>
</beans>
但是,对于生产用途,您可能想要调整主机或密码等设置,如以下示例所示:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="server" p:port="6379" />
</beans>
5.3.3. 配置 JRedis 连接器(从 1.7 开始不推荐使用)
JRedis是 Spring Data Redis 通过org.springframework.data.redis.connection.jredis
软件包支持的另一个流行的开源连接器。
典型的 JRedis 配置如下所示:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jredisConnectionFactory" class="org.springframework.data.redis.connection.jredis.JredisConnectionFactory" p:host-name="server" p:port="6379"/>
</beans>
该配置与 Jedis 非常相似,但有一个 exception。默认情况下,JedisConnectionFactory
合并连接。为了在 JRedis 中使用连接池,请使用JredisPool
的实例配置JredisConnectionFactory
。例如:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jredisConnectionFactory" class="org.springframework.data.redis.connection.jredis.JredisConnectionFactory">
<constructor-arg>
<bean class="org.springframework.data.redis.connection.jredis.DefaultJredisPool">
<constructor-arg value="localhost" />
<constructor-arg value="6379" />
</bean>
</constructor-arg>
</bean>
</beans>
5.3.4. 配置 SRP 连接器(从 1.7 开始不推荐使用)
SRP(Sam 的 Redis 协议的缩写)是 Spring Data Redis 通过org.springframework.data.redis.connection.srp
包支持的第三个开源连接器。
现在,它的配置可能很容易猜到:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="srpConnectionFactory" class="org.springframework.data.redis.connection.srp.SrpConnectionFactory" p:host-name="server" p:port="6379"/>
</beans>
不用说,该配置与其他连接器的配置非常相似。
5.3.5. 配置 Lettuce 连接器
Lettuce是 Spring Data Redis 通过org.springframework.data.redis.connection.lettuce
软件包支持的第四个开源连接器。
它的配置可能很容易猜到:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="lettuceConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory" p:host-name="server" p:port="6379"/>
</beans>
还可以调整一些 Lettuce 特定的连接参数。默认情况下,由LettuceConnectionFactory
创建的所有LettuceConnection
共享所有非阻塞和非事务性操作的相同线程安全本机连接。将shareNativeConnection
设置为 false 可以每次使用专用连接。 LettuceConnectionFactory
还可以配置为LettucePool
以用于池化阻止和事务连接,或者如果shareNativeConnection
设置为 false,则用于所有连接。
5.4. Redis 前哨支持
为了处理高可用性 Redis,Spring Data Redis 使用RedisSentinelConfiguration
支持Redis Sentinel,如以下示例所示:
/**
* 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);
}
Configuration Properties
RedisSentinelConfiguration
也可以使用PropertySource
定义,您可以设置以下属性:
-
spring.redis.sentinel.master
:主节点的名称。 -
spring.redis.sentinel.nodes
:以逗号分隔的 host:port 对列表。
有时,需要与哨兵之一直接互动。使用RedisConnectionFactory.getSentinelConnection()
或RedisConnection.getSentinelCommands()
可让您访问配置的第一个活动 Sentinel。
5.5. 通过 RedisTemplate 处理对象
大多数用户可能会使用RedisTemplate
及其对应的软件包org.springframework.data.redis.core
。实际上,由于模板具有丰富的功能集,因此它是 Redis 模块的中心类。该模板为 Redis 交互提供了高级抽象。 RedisConnection
提供了接受和返回二进制值(byte
数组)的低级方法,而模板负责序列化和连接 Management,使用户不必处理此类细节。
此外,该模板提供了操作视图(按照 Redis 命令reference的分组),提供了丰富的通用接口,用于针对某种类型或某些键(通过KeyBound
接口),如下表所述:
表 1.操作视图
Interface | Description |
---|---|
键类型操作 | |
GeoOperations | Redis 地理空间操作,例如GEOADD ,GEORADIUS ,… |
HashOperations | Redis 哈希操作 |
HyperLogLogOperations | Redis HyperLogLog 操作,例如PFADD ,PFCOUNT ,… |
ListOperations | Redis 列表操作 |
SetOperations | Redis 设置操作 |
ValueOperations | Redis 字符串(或值)操作 |
ZSetOperations | Redis zset(或排序集)操作 |
HashOperations | Redis 哈希操作 |
HyperLogLogOperations | Redis HyperLogLog 操作,例如(pfadd,pfcount 等) |
GeoOperations | Redis 地理空间操作,例如GEOADD ,GEORADIUS ,…) |
关键绑定操作 | |
BoundGeoOperations | Redis 键绑定地理空间操作 |
BoundHashOperations | Redis 哈希键绑定操作 |
BoundKeyOperations | Redis 按键绑定操作 |
BoundListOperations | Redis 列表键绑定操作 |
BoundSetOperations | Redis 设置键绑定操作 |
BoundValueOperations | Redis 字符串(或值)键绑定操作 |
BoundZSetOperations | Redis zset(或排序集)键绑定操作 |
BoundHashOperations | Redis 哈希键绑定操作 |
BoundGeoOperations | Redis 键绑定地理空间操作。 |
配置后,该模板是线程安全的,并且可以在多个实例之间重用。
RedisTemplate
使用大多数操作基于 Java 的序列化程序。这意味着模板编写或读取的任何对象都将通过 Java 进行序列化和反序列化。您可以在模板上更改序列化机制,Redis 模块提供了几种实现,可以在org.springframework.data.redis.serializer
包中找到。有关更多信息,请参见Serializers。您还可以将enableDefaultSerializer
属性设置为false
,将任何序列化器设置为 null 并将 RedisTemplate 与原始字节数组一起使用。请注意,模板要求所有键都不为空。但是,只要基础串行器接受这些值,它们就可以为空。阅读每个序列化程序的 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 http://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());
}
}
5.6. 以字符串为中心的便利类
由于 Redis 中存储的键和值是java.lang.String
很常见,因此 Redis 模块提供了RedisConnection
和RedisTemplate
的两个扩展,分别是StringRedisConnection
(及其DefaultStringRedisConnection
实现)和StringRedisTemplate
,作为密集型 String 操作的便捷一站式解决方案。除了绑定到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 http://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
使您可以通过RedisCallback
接口直接与 Redis 对话。此功能直接与RedisConnection
对话,因此可以完全控制您。请注意,当使用StringRedisTemplate
时,回调将接收StringRedisConnection
的实例。以下示例显示了如何使用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");
}
});
}
5.7. Serializers
从框架的角度来看,Redis 中存储的数据仅为字节。尽管 Redis 本身支持各种类型,但在大多数情况下,它们是指数据的存储方式而不是数据的表示方式。由用户决定是否将信息转换为字符串或任何其他对象。
在 Spring Data 中,用户(自定义)类型和原始数据之间的转换(反之亦然)由org.springframework.data.redis.serializer
包中的 Redis 处理。
该软件包包含两种类型的序列化器,顾名思义,它们负责序列化过程:
-
基于
RedisSerializer
的两路串行器。 -
使用
RedisElementReader
和RedisElementWriter
的元素读取器和写入器。
这些变体之间的主要区别在于,RedisSerializer
主要序列化为byte[]
,而读写器使用ByteBuffer
。
有多种实现方式(包括本文档中已经提到的两种):
-
JdkSerializationRedisSerializer
,默认情况下用于RedisCache
和RedisTemplate
。 -
StringRedisSerializer
。
但是,可以通过 Spring OXM支持将OxmSerializer
用于对象/ XMLMap,或者将Jackson2JsonRedisSerializer
或GenericJackson2JsonRedisSerializer
用于以JSON格式存储数据。
请注意,存储格式不仅限于值。它可以不受限制地用于键,值或哈希。
Warning
默认情况下,RedisCache
和RedisTemplate
被配置为使用 Java 本机序列化。众所周知,Java 本机序列化可允许由有效载荷引起的远程代码执行,有效载荷利用易受攻击的库和类注入未验证的字节码。在反序列化步骤中,操纵 Importing 可能导致应用程序中不需要的代码执行。因此,请勿在不受信任的环境中使用序列化。通常,我们强烈建议您使用其他任何消息格式(例如 JSON)。
如果您担心由 Java 序列化引起的安全漏洞,请考虑在核心 JVM 级别上使用通用序列化筛选器机制,该机制最初是为 JDK 9 开发的,但后来又移植到 JDK 8、7 和 6:
5.8. 哈希 Map
可以通过在 Redis 中使用各种数据结构来存储数据。 Jackson2JsonRedisSerializer
可以JSON格式转换对象。理想情况下,可以使用纯键将 JSON 存储为值。您可以使用 Redis 散列来实现结构化对象的更复杂 Map。 Spring Data Redis 提供了各种将数据 Map 到哈希的策略(取决于用例):
-
通过使用
HashOperations
和serializer直接 Map -
Using Redis Repositories
-
使用
HashMapper
和HashOperations
5.8.1. 哈希 Map 器
哈希 Map 器是 Map 对象到Map<K, V>
并返回的转换器。 HashMapper
适用于 Redis 哈希。
有多种实现方式:
-
BeanUtilsHashMapper
使用 Spring 的BeanUtils。 -
ObjectHashMapper
使用Object-to-Hash Mapping。
以下示例显示了一种实现哈希 Map 的方法:
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);
}
}
5.8.2. Jackson2HashMapper
Jackson2HashMapper
通过使用FasterXML Jackson为域对象提供 Redis 哈希 Map。 Jackson2HashMapper
可以将顶级属性 Map 为哈希字段名称,并且可以选择将结构展平。简单类型 Map 为简单值。复杂类型(嵌套对象,集合,Map 等)表示为嵌套 JSON。
展平为所有嵌套属性创建单个哈希条目,并尽可能将复杂类型解析为简单类型。
考虑以下类及其包含的数据结构:
public class Person {
String firstname;
String lastname;
Address address;
}
public class Address {
String city;
String country;
}
下表显示了上一类中的数据在法线 Map 中的显示方式:
表 2.法线 Map
Hash Field | Value |
---|---|
firstname | Jon |
lastname | Snow |
address | { "city" : "Castle Black", "country" : "The North" } |
下表显示了上一类中的数据在平面 Map 中的显示方式:
表 3.平面 Map
Hash Field | Value |
---|---|
firstname | Jon |
lastname | Snow |
address.city | Castle Black |
address.country | The North |
Note
拼合要求所有属性名称都不得干扰 JSON 路径。使用展平时,不支持在 Map 键中使用圆点或方括号或将其用作属性名称。生成的哈希不能 Map 回一个对象。
5.9. Redis 消息传递(Pub/Sub)
Spring Data 为 Redis 提供了专用的消息传递集成,其功能和命名与 Spring Framework 中的 JMS 集成相似。
Redis 消息传递可以大致分为两个功能区域:
-
消息的发布或产生
-
订阅或消费消息
这是通常称为“发布/订阅”(简称“发布/订阅”)的模式的示例。 RedisTemplate
类用于消息生成。对于类似于 Java EE 的消息驱动 bean 样式的异步接收,Spring Data 提供了专用的消息侦听器容器,该容器用于创建消息驱动的 POJO(MDP),并为同步接收提供RedisConnection
协定。
org.springframework.data.redis.connection
和org.springframework.data.redis.listener
软件包提供 Redis 消息传递的核心功能。
5.9.1. 发布(发送消息)
要发布消息,可以与其他操作一起使用低级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");
5.9.2. 订阅(接收消息)
在接收方,可以通过直接命名一个 Channels 或多个 Channels 或使用模式匹配来订阅一个或多个 Channels。后一种方法非常有用,因为它不仅允许使用一个命令创建多个订阅,而且还可以侦听在订阅时尚未创建的 Channels(只要它们与模式匹配)。
在低层,RedisConnection
提供subscribe
和pSubscribe
方法,它们 MapRedis 命令以分别按通道或按模式进行预订。注意,可以将多个通道或模式用作参数。要更改连接的预订或查询连接是否正在侦听,RedisConnection
提供了getSubscription
和isSubscribed
方法。
Note
Spring Data Redis 中的订阅命令被阻止。也就是说,在连接上调用订阅会导致当前线程在开始 await 消息时阻塞。仅当取消订阅时才释放线程,这是在另一个线程在 same 连接上调用unsubscribe
或pUnsubscribe
时发生的。有关此问题的解决方案,请参见“ 消息侦听器容器”(本文档后面)。
如前所述,一旦订阅,连接即开始 await 消息。仅允许添加新订阅,修改现有订阅和取消现有订阅的命令。调用subscribe
,pSubscribe
,unsubscribe
或pUnsubscribe
以外的任何东西都会引发异常。
为了订阅消息,需要实现MessageListener
回调。每次收到新消息时,都会通过onMessage
方法调用回调并运行用户代码。该接口不仅可以访问实际消息,还可以访问已接收消息的通道,以及订阅使用的与通道匹配的模式(如果有)。该信息使被叫方不仅可以按内容来区分各种消息,还可以检查其他细节。
邮件监听器容器
由于其阻塞性质,低级别订阅没有吸引力,因为它要求每个侦听器都进行连接和线程 Management。为了减轻这个问题,Spring Data 提供了RedisMessageListenerContainer
,它可以完成所有繁重的工作。如果您熟悉 EJB 和 JMS,则应该熟悉这些概念,因为它被设计为尽可能接近 Spring Framework 及其消息驱动的 POJO(MDP)的支持。
RedisMessageListenerContainer
充当消息侦听器容器。它用于从 Redis 通道接收消息并驱动注入到其中的MessageListener
实例。侦听器容器负责消息接收的所有线程,并分派到侦听器中进行处理。消息侦听器容器是 MDP 与消息传递提供程序之间的中介,并负责注册接收消息,资源获取和释放,异常转换等。这使您作为应用程序开发人员可以编写与接收消息(并对消息做出响应)相关的(可能很复杂)业务逻辑,并将样板 Redis 基础结构问题委托给框架。
此外,为了最大程度地减少应用程序占用空间,RedisMessageListenerContainer
允许一个连接和一个线程由多个侦听器共享,即使它们不共享订阅。因此,无论应用程序跟踪多少个侦听器或通道,运行时间成本在整个生命周期中都保持不变。此外,该容器允许更改运行时配置,以便您可以在应用程序运行时添加或删除侦听器,而无需重新启动。此外,容器使用惰性订阅方法,仅在需要时使用RedisConnection
。如果所有侦听器都未订阅,则将自动执行清除,然后释放线程。
为了帮助消息的异步特性,容器需要一个java.util.concurrent.Executor
(或 Spring 的TaskExecutor
)来调度消息。根据负载,侦听器的数量或运行时环境,应更改或调整执行程序,以更好地满足您的需求。特别是在托管环境(例如应用程序服务器)中,强烈建议选择适当的TaskExecutor
以利用其运行时。
The MessageListenerAdapter
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
类型的 内容 来强类型化。此外,可以将发送消息的通道或模式作为String
类型的第二个参数传递给该方法:
public class DefaultMessageDelegate implements MessageDelegate {
// implementation elided for clarity...
}
请注意,上面的MessageDelegate
接口(上面的DefaultMessageDelegate
类)实现完全没有** Redis 依赖项。这确实是我们使用以下配置制作成 MDP 的 POJO:
<?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>
Note
侦听器主题可以是 Channels(例如topic="chatroom"
)或模式(例如topic="*room"
)
前面的示例使用 Redis 命名空间声明消息侦听器容器,并自动将 POJO 注册为侦听器。完整的定义如下:
<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
)在底层格式和所需对象类型之间进行转换。容器捕获并处理由方法调用引起的任何异常(默认情况下,异常会被记录)。
5.10. RedisTransaction
Redis 通过multi
,exec
和discard
命令提供对transactions的支持。这些操作在RedisTemplate
上可用。但是,不能保证RedisTemplate
使用同一连接执行事务中的所有操作。
当需要对同一connection
执行多项操作时(例如,使用 Redis 事务时),Spring Data Redis 提供了SessionCallback
接口。下面的示例使用multi
方法:
//execute a transaction
List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForSet().add("key", "value1");
// This will contain the results of all operations in the transaction
return operations.exec();
}
});
System.out.println("Number of items added to set: " + txResults.get(0));
RedisTemplate
使用其值,哈希键和哈希值序列化程序在返回之前反序列化exec
的所有结果。还有一个exec
方法,可让您传递自定义序列化程序以获取事务结果。
Note
从 1.1 版开始,对RedisConnection
和RedisTemplate
的exec
方法进行了重要更改。以前,这些方法直接从连接器返回事务处理的结果。这意味着数据类型通常不同于RedisConnection
方法返回的数据类型。例如,zAdd
返回一个布尔值,指示该元素是否已添加到排序集中。大多数连接器都会将此值返回为 long,而 Spring Data Redis 将执行转换。另一个共同的区别是,大多数连接器都会为set
之类的操作返回状态回复(通常是字符串OK
)。这些答复通常被 Spring Data Redis 丢弃。在 1.1 之前的版本中,未对exec
的结果执行这些转换。另外,结果未在RedisTemplate
中反序列化,因此它们通常包括原始字节数组。如果此更改使您的应用程序中断,请在RedisConnectionFactory
上将convertPipelineAndTxResults
设置为false
以禁用此行为。
5.10.1. @Transaction 支持
默认情况下,事务支持是禁用的,必须通过设置setEnableTransactionSupport(true)
为使用中的每个RedisTemplate
显式启用。这样做会强制将当前RedisConnection
绑定到触发MULTI
的当前Thread
。如果事务顺利完成,则调用EXEC
。否则调用DISCARD
。一旦进入MULTI
,RedisConnection
就将写入操作排队。所有readonly
操作(例如KEYS
)都通过管道传递到新的(非线程绑定的)RedisConnection
。
以下示例显示如何配置事务 Management:
示例 1.启用事务 Management 的配置
@Configuration
@EnableTransactionManagement (1)
public class RedisTxContextConfiguration {
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// explicitly enable transaction support
template.setEnableTransactionSupport(true); (2)
return template;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// jedis || Lettuce || srp || ...
}
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource()); (3)
}
@Bean
public DataSource dataSource() throws SQLException {
// ...
}
}
- (1) 将 Spring Context 配置为启用声明式 TransactionManagement。
- (2) 通过将连接绑定到当前线程来配置
RedisTemplate
参与事务。 - (3) TransactionManagement 需要
PlatformTransactionManager
。 Spring Data Redis 不附带PlatformTransactionManager
实现。假设您的应用程序使用 JDBC,Spring Data Redis 可以使用现有的事务 Management 器参与事务。
以下示例每个都演示了使用约束:
例子 2.使用约束
// must be performed on thread-bound connection
template.opsForValue().set("thing1", "thing2");
// read operation must be executed on a free (not transaction-aware) connection
template.keys("*");
// returns null as values set within a transaction are not visible
template.opsForValue().get("thing1");
5.11. Pipelining
Redis 提供对pipelining的支持,该支持涉及将多个命令发送到服务器,而无需 await 答复,然后一步即可读取答复。当您需要连续发送多个命令(例如将多个元素添加到同一 List)时,流水线可以提高性能。
Spring Data Redis 提供了几种RedisTemplate
方法来执行管道中的命令。如果您不关心流水线操作的结果,则可以使用标准的execute
方法,将true
传递给pipeline
参数。 executePipelined
方法在管道中运行提供的RedisCallback
或SessionCallback
并返回结果,如以下示例所示:
//pop a specified number of items from a queue
List<Object> results = stringRedisTemplate.executePipelined(
new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
for(int i=0; i< batchSize; i++) {
stringRedisConn.rPop("myqueue");
}
return null;
}
});
前面的示例从管道中的队列中运行项目的批量右弹出。 results
List
包含所有弹出项目。 RedisTemplate
使用其值,哈希键和哈希值序列化程序在返回之前对所有结果进行反序列化,因此前面示例中返回的项目为字符串。还有其他executePipelined
方法,可让您传递自定义序列化程序以获取流水线结果。
请注意,从RedisCallback
返回的值必须为 null,因为为了支持返回管道命令的结果而将其丢弃。
Note
从 1.1 版开始,对RedisConnection
和RedisTemplate
的exec
方法进行了重要更改。以前,这些方法直接从连接器返回事务处理的结果。这意味着数据类型通常不同于RedisConnection
方法返回的数据类型。例如,zAdd
返回一个布尔值,指示该元素是否已添加到排序集中。大多数连接器都会将此值返回为 long,而 Spring Data Redis 将执行转换。另一个共同的区别是,大多数连接器都会为set
之类的操作返回状态回复(通常是字符串OK
)。这些答复通常被 Spring Data Redis 丢弃。在 1.1 之前的版本中,未对exec
的结果执行这些转换。另外,结果未在RedisTemplate
中反序列化,因此它们通常包括原始字节数组。如果此更改使您的应用程序中断,请在RedisConnectionFactory
上将convertPipelineAndTxResults
设置为false
以禁用此行为。
5.12. Redis 脚本
Redis 2.6 及更高版本通过eval和evalsha命令为执行 Lua 脚本提供支持。 Spring Data Redis 为脚本执行提供了高级抽象,该抽象可处理序列化并自动使用 Redis 脚本缓存。
可以通过调用RedisTemplate
的execute
方法来运行脚本。它使用可配置的ScriptExecutor
来运行提供的脚本。默认情况下,ScriptExecutor
负责序列化提供的键和参数并反序列化脚本结果。这是通过模板的键和值序列化程序完成的。还有一个额外的重载,可让您传递脚本参数和结果的自定义序列化程序。
默认的ScriptExecutor
通过检索脚本的 SHA1 并首先尝试运行evalsha
来优化性能,如果 Redis 脚本缓存中还没有该脚本,则回退到eval
。
下面的示例通过使用 Lua 脚本来运行常见的“检查并设置”方案。这是 Redis 脚本的理想用例,因为它需要原子地运行一组命令,并且一个命令的行为会受到另一个命令的结果的影响。
@Bean
public RedisScript<Boolean> script() {
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<Boolean>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua")));
redisScript.setResultType(Boolean.class);
}
public class Example {
@Autowired
RedisScript<Boolean> script;
public boolean checkAndSet(String expectedValue, String newValue) {
return redisTemplate.execute(script, Collections.singletonList("key"), expectedValue, newValue);
}
}
-- checkandset.lua
local current = redis.call('GET', KEYS[1])
if current == ARGV[1]
then redis.call('SET', KEYS[1], ARGV[2])
return true
end
return false
前面的代码配置RedisScript
指向名为checkandset.lua
的文件,该文件应返回布尔值。脚本resultType
应该是Long
,Boolean
,List
或反序列化值类型之一。如果脚本返回抛弃状态(特别是OK
),则它也可以是null
。
Tip
理想的是在应用程序上下文中配置一个DefaultRedisScript
实例,以避免在每次执行脚本时重新计算脚本的 SHA1.
然后,上面的checkAndSet
方法运行脚本。脚本可以在SessionCallback
内作为事务或管道的一部分运行。有关更多信息,请参见“ Redis Transactions”和“ Pipelining”。
Spring Data Redis 提供的脚本支持还允许您使用 Spring Task 和 Scheduler 抽象来调度 Redis 脚本以定期执行。有关更多详细信息,请参见Spring Framework文档。
5.13. 支持类
软件包org.springframework.data.redis.support
提供了各种可重用的组件,这些组件依赖 Redis 作为后备存储。当前,该软件包在 Redis 之上包含各种基于 JDK 的接口实现,例如atomic计数器和 JDK Collections。
通过原子计数器,可以轻松包装 Redis 密钥增量,而集合则可以轻松 ManagementRedis 密钥,并最大程度地减少存储风险或 API 泄漏。特别是,RedisSet
和RedisZSet
接口可轻松访问 Redis 支持的设置操作,例如intersection
和union
。 RedisList
在 Redis 上实现List
,Queue
和Deque
Contract(及其等效的阻塞同级),将存储暴露为 FIFO(先进先出),LIFO(先入先出)或封顶只需最少的配置即可收集。以下示例显示了使用RedisList
的 bean 的配置:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="queue" class="org.springframework.data.redis.support.collections.DefaultRedisList">
<constructor-arg ref="redisTemplate"/>
<constructor-arg value="queue-key"/>
</bean>
</beans>
以下示例显示了Deque
的 Java 配置示例:
public class AnotherExample {
// injected
private Deque<String> queue;
public void addTag(String tag) {
queue.push(tag);
}
}
如前面的示例所示,使用代码与实际的存储实现分离。实际上,没有迹象表明在下面使用了 Redis。这使得从开发环境到生产环境的迁移变得透明,并大大提高了可测试性(Redis 实现可以用内存中的实现代替)。
5.13.1. 支持 Spring Cache 抽象
Spring Redis 通过org.springframework.data.redis.cache
包为 Spring cache abstraction提供了一个实现。要将 Redis 用作后备实现,只需将RedisCacheManager
添加到您的配置中:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- turn on declarative caching -->
<cache:annotation-driven />
<!-- declare Redis Cache Manager -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager" c:template-ref="redisTemplate"/>
</beans>
Note
默认情况下,每当请求Cache
时,RedisCacheManager
就会延迟初始化RedisCache
。可以通过 sched 义Set
缓存名称来更改此设置。
Note
默认情况下,RedisCacheManager
将不参与任何正在进行的 Transaction。使用setTransactionAware
启用 Transaction 支持。
Note
默认情况下,RedisCacheManager
不会为缓存区域添加键前缀,这会导致用于维护已知键的ZSET
意外增长。强烈建议启用前缀的使用,以避免使用多个缓存区域的这种意外增长和潜在的密钥冲突。
Note
默认情况下,RedisCache
不会将任何null
个值作为键缓存,而 Redis 本身不会丢弃该值。但是,您可以通过RedisCacheManager
显式启用null
值缓存,这会将org.springframework.cache.support.NullValue
存储为占位符。
6. Redis 群集
使用Redis Cluster需要 Redis Server 3.0 版。有关更多信息,请参见Cluster Tutorial。
6.1. 启用 Redis 集群
群集支持基于与非群集通信相同的构造块。 RedisClusterConnection
是RedisConnection
的扩展,用于处理与 Redis 群集的通信,并将错误转换为 Spring DAO 异常层次结构。 RedisClusterConnection
实例是使用RedisConnectionFactory
创建的,必须使用关联的RedisClusterConfiguration
进行设置,如以下示例所示:
例子 3. Redis 集群的示例 RedisConnectionFactory 配置
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterConfigurationProperties {
/*
* spring.redis.cluster.nodes[0] = 127.0.0.1:7379
* spring.redis.cluster.nodes[1] = 127.0.0.1:7380
* ...
*/
List<String> nodes;
/**
* Get initial collection of known cluster nodes in format {@code host:port}.
*
* @return
*/
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}
@Configuration
public class AppConfig {
/**
* Type safe representation of application.properties
*/
@Autowired ClusterConfigurationProperties clusterProperties;
public @Bean RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(
new RedisClusterConfiguration(clusterProperties.getNodes()));
}
}
Configuration Properties
RedisClusterConfiguration
也可以通过PropertySource
进行定义,并具有以下属性:
-
spring.redis.cluster.nodes
:以逗号分隔的 host:port 对列表。 -
spring.redis.cluster.max-redirects
:允许的群集重定向数。
Note
初始配置将驱动程序库指向一组初始的群集节点。由实时群集重新配置导致的更改仅保留在本机驱动程序中,而不会写回到配置中。
6.2. 使用 Redis 集群连接
如前所述,Redis Cluster 的行为不同于单节点 Redis 甚至是 Sentinel 监视的主从环境。这是因为自动分片将密钥 Map 到 16384 个插槽之一,该插槽分布在节点上。因此,涉及多个键的命令必须 assert 所有键都 Map 到完全相同的插槽,以避免跨插槽执行错误。单个群集节点仅提供一组专用密钥。针对一台特定服务器发出的命令仅针对该服务器提供的那些键返回结果。作为一个简单的示例,请考虑KEYS
命令。当在集群环境中发布给服务器时,它仅返回请求发送到的节点所服务的密钥,而不一定返回集群中的所有密钥。因此,要在群集环境中获取所有密钥,必须从所有已知的主节点读取密钥。
虽然将特定密钥重定向到相应的插槽服务节点是由驱动程序库处理的,但RedisClusterConnection
涵盖了更高级别的功能,例如跨节点收集信息或向集群中的所有节点发送命令。从前面的示例中拿起键示例,这意味着keys(pattern)
方法将拾取群集中的每个主节点,并同时在每个主节点上执行KEYS
命令,同时拾取结果并返回累积的键集。仅请求单个节点的键RedisClusterConnection
会为那些方法(例如keys(node, pattern)
)提供重载。
RedisClusterNode
可以从RedisClusterConnection.clusterGetNodes
获得,也可以使用主机和端口或节点 ID 来构造。
以下示例显示了在集群中运行的一组命令:
例子 4.跨集群运行命令的 samples
[emailprotected]:7379 > cluster nodes
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 (1)
7bb78c... 127.0.0.1:7380 master - 0 1449730618304 2 connected 5461-10922 (2)
164888... 127.0.0.1:7381 master - 0 1449730618304 3 connected 10923-16383 (3)
b8b5ee... 127.0.0.1:7382 slave 6b38bb... 0 1449730618304 25 connected (4)
RedisClusterConnection connection = connectionFactory.getClusterConnnection();
connection.set("thing1", value); (5)
connection.set("thing2", value); (6)
connection.keys("*"); (7)
connection.keys(NODE_7379, "*"); (8)
connection.keys(NODE_7380, "*"); (9)
connection.keys(NODE_7381, "*"); (10)
connection.keys(NODE_7382, "*"); (11)
- (1) 服务于插槽 0 到 5460 的主节点在 7382 被复制到从节点
- (2) 主节点服务于插槽 5461 至 10922
- (3) 主节点服务于插槽 10923 至 16383
- (4) 拥有 7379 的主节点副本的从节点
- (5) 请求路由到 7381 服务插槽 12182 的节点
- (6) 请求路由到 7379 服务于 5061 插槽的节点
- (7) 请求已路由到 7379、7380、7381 的节点→[thing1,thing2]
- (8) 请求已路由到 7379→[thing2]的节点
- (9) 请求路由到 7380→[]的节点
- (10) 请求路由到 7381→[thing1]的节点
- (11) 请求已路由到 7382→[thing2]的节点
当所有键都 Map 到同一插槽时,本机驱动程序库会自动处理跨插槽请求,例如MGET
。但是,一旦不是这种情况,RedisClusterConnection
将对插槽服务节点执行多个并行GET
命令,然后再次返回累加结果。这比单插槽执行的性能要差,因此应谨慎使用。如有疑问,请考虑通过在大括号中提供前缀(例如{my-prefix}.thing1
和{my-prefix}.thing2
)将密钥固定到同一插槽,这两者都将 Map 到相同的插槽号。以下示例显示了跨槽请求处理:
例子 5.跨槽请求处理的 samples
[emailprotected]:7379 > cluster nodes
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 (1)
7bb...
RedisClusterConnection connection = connectionFactory.getClusterConnnection();
connection.set("thing1", value); // slot: 12182
connection.set("{thing1}.thing2", value); // slot: 12182
connection.set("thing2", value); // slot: 5461
connection.mGet("thing1", "{thing1}.thing2"); (2)
connection.mGet("thing1", "thing2"); (3)
- (1) 与之前的示例中的配置相同。
- (2) 键 Map 到相同的插槽→127.0.0.1:7381 MGET Thing1{thing1} .thing2
- (3) 密钥 Map 到不同的插槽,并拆分为单个插槽,并路由到相应的节点
→127.0.0.1:7379 GET Thing2
→127.0.0.1:7381 GET something1
Tip
前面的示例演示了 Spring Data Redis 遵循的一般策略。请注意,某些操作可能需要将大量数据加载到内存中才能计算所需的命令。此外,并非所有跨槽请求都可以安全地移植到多个单个槽请求中,如果滥用(例如PFCOUNT
),则会出错。
6.3. 使用 RedisTemplate 和 ClusterOperations
有关RedisTemplate
的一般用途,配置和用法的信息,请参见通过 RedisTemplate 处理对象部分。
Warning
使用任何 Json RedisSerializers
设置RedisTemplate#keySerializer
时请务必小心,因为更改 JSON 结构会立即影响哈希槽的计算。
RedisTemplate
通过ClusterOperations
接口提供对特定于群集的操作的访问权限,该接口可以从RedisTemplate.opsForCluster()
获得。这使您可以在群集内的单个节点上显式运行命令,同时保留为模板配置的序列化和反序列化功能。它还提供 Management 命令(例如CLUSTER MEET
)或更高级的操作(例如重新分片)。
以下示例显示了如何使用RedisTemplate
访问RedisClusterConnection
:
例子 6.用RedisTemplate
访问RedisClusterConnection
ClusterOperations clusterOps = redisTemplate.opsForCluster();
clusterOps.shutdown(NODE_7379); (1)
- (1) 在 7379 处关闭节点,然后用手指交叉放置一个可以接管的从站。
7. Redis 存储库
通过使用 Redis 信息库,您可以无缝地在 Redis 哈希中转换和存储域对象,应用自定义 Map 策略以及使用二级索引。
Tip
Redis 存储库至少需要 Redis Server 版本 2.8.0,并且不适用于事务。确保将RedisTemplate
与禁用 Transaction 支持一起使用。
7.1. Usage
Spring Data Redis 使您可以轻松实现域实体,如以下示例所示:
例子 7.samples 人实体
@RedisHash("people")
public class Person {
@Id String id;
String firstname;
String lastname;
Address address;
}
这里有一个非常简单的域对象。请注意,它的类型带有@RedisHash
Comments,并带有org.springframework.data.annotation.Id
Comments 的名为id
的属性。这两个项目负责创建用于保留哈希的实际密钥。
Note
用@Id
Comments 的属性以及名为id
的属性被视为标识符属性。那些带有 Comments 的人比其他人更受青睐。
现在要 true 拥有负责存储和检索的组件,我们需要定义一个存储库接口,如以下示例所示:
例子 8.持久人实体的基本存储库接口
public interface PersonRepository extends CrudRepository<Person, String> {
}
随着我们的存储库CrudRepository
的扩展,它提供了基本的 CRUD 和 finder 操作。我们需要将它们粘合在一起的是对应的 Spring 配置,如以下示例所示:
例子 9.用于 Redis 存储库的 JavaConfig
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {
@Bean
public RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
return template;
}
}
给定前面的设置,我们可以将PersonRepository
注入到我们的组件中,如以下示例所示:
例子 10.访问个人实体
@Autowired PersonRepository repo;
public void basicCrudOperations() {
Person rand = new Person("rand", "al'thor");
rand.setAddress(new Address("emond's field", "andor"));
repo.save(rand); (1)
repo.findOne(rand.getId()); (2)
repo.count(); (3)
repo.delete(rand); (4)
}
- (1) 如果当前值为
null
或重新使用已经设置的id
值并在 Redis 哈希中使用keyspace:id
模式的键存储类型Person
的属性,则生成一个新的id
-在这种情况下,它可能会是people:5d67b7e1-8640-4475-beeb-c666fab4c0e5
。 - (2) 使用提供的
id
检索存储在keyspace:id
的对象。 - (3) 对
Person
上的@RedisHash
定义的键空间people
中可用的实体总数进行计数。 - (4) 从 Redis 移除给定对象的键。
7.2. 对象到哈希的 Map
Redis 存储库支持将对象保留为哈希。这需要由RedisConverter
完成的对象到哈希的转换。默认实现使用Converter
将属性值 Map 到 Redis 本机byte[]
或从 Redis 本机byte[]
Map 属性值。
给定前面几节中的Person
类型,默认 Map 如下所示:
_class = org.example.Person (1)
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand (2)
lastname = al'thor
address.city = emond's field (3)
address.country = andor
- (1)
_class
属性包括在根级别以及任何嵌套接口或抽象类型中。 - (2) 简单属性值是按路径 Map 的。
- (3) 复杂类型的属性按其点路径进行 Map。
下表描述了默认 Map 规则:
表 4.默认 Map 规则
Type | Sample | Mapped Value |
---|---|---|
Simple Type | ||
(例如,String) | String firstname =“ rand”; | firstname =“ rand” |
Complex Type (例如,地址) | 地址地址=新地址(“ emond 的字段”); | address.city =“ emond 的字段” |
List 的简单类型 | 列表\ 昵称= asList(“ dragon reborn”,“ lews therin”); | 昵称。[0] =“龙再生”, 昵称。[1] =“留下 therin” |
Map 类型 | Map<String, String> atts = asMap(\ {"eye-color", "grey"},{“… | atts。[eye-color] =” grey“, atts。[hair-color] =“… |
List 的复杂类型 | 列表\ 地址= asList(新地址(“ em… | 地址。[0] .city =” emond 的字段“, 地址。[1] .city =“… |
Map 的复杂类型 | Map\ <String, Address>地址= asMap({“ home”,新的地址(“ em… | addresses。[home] .city =” emond 的字段“, 地址。[work] .city =“… |
Warning
由于采用平面表示结构,因此 Map 键必须是简单的类型,例如String
或Number
。
可以通过在RedisCustomConversions
中注册相应的Converter
来自定义 Map 行为。这些转换器可以处理从单个byte[]
到Map<String,byte[]>
的转换。第一个适用于(例如)将复杂类型转换为(例如)仍使用默认 Map 哈希结构的二进制 JSON 表示形式。第二个选项提供对生成的哈希的完全控制。
Warning
将对象写入 Redis 哈希将删除哈希中的内容,并重新创建整个哈希,因此,尚未 Map 的数据将丢失。
下面的示例显示了两个示例字节数组转换器:
例子 11.samplesbyte []转换器
@WritingConverter
public class AddressToBytesConverter implements Converter<Address, byte[]> {
private final Jackson2JsonRedisSerializer<Address> serializer;
public AddressToBytesConverter() {
serializer = new Jackson2JsonRedisSerializer<Address>(Address.class);
serializer.setObjectMapper(new ObjectMapper());
}
@Override
public byte[] convert(Address value) {
return serializer.serialize(value);
}
}
@ReadingConverter
public class BytesToAddressConverter implements Converter<byte[], Address> {
private final Jackson2JsonRedisSerializer<Address> serializer;
public BytesToAddressConverter() {
serializer = new Jackson2JsonRedisSerializer<Address>(Address.class);
serializer.setObjectMapper(new ObjectMapper());
}
@Override
public Address convert(byte[] value) {
return serializer.deserialize(value);
}
}
使用前面的字节数组Converter
会产生类似于以下内容的输出:
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al'thor
address = { city : "emond's field", country : "andor" }
以下示例显示了Map
转换器的两个示例:
例子 12.示例图\ <String,byte[]>转换器
@WritingConverter
public class AddressToMapConverter implements Converter<Address, Map<String,byte[]>> {
@Override
public Map<String,byte[]> convert(Address source) {
return singletonMap("ciudad", source.getCity().getBytes());
}
}
@ReadingConverter
public class MapToAddressConverter implements Converter<Address, Map<String, byte[]>> {
@Override
public Address convert(Map<String,byte[]> source) {
return new Address(new String(source.get("ciudad")));
}
}
使用前面的 Map Converter
会产生类似于以下内容的输出:
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al'thor
ciudad = "emond's field"
Note
自定义转换对索引解析没有影响。即使对于自定义转换类型,仍会创建Secondary Indexes。
7.2.1. 自定义类型 Map
如果要避免将整个 Java 类名称写为类型信息,并且希望使用键,则可以在要保留的实体类上使用@TypeAlias
注解。如果您需要进一步自定义 Map,请查看TypeInformationMapper界面。该接口的实例可以在DefaultRedisTypeMapper
上配置,该实例可以在MappingRedisConverter
上配置。
以下示例显示如何为实体定义类型别名:
例子 13.为一个实体定义@TypeAlias
@TypeAlias("pers")
class Person {
}
生成的文档包含pers
作为_class
字段中的值。
配置自定义类型 Map
以下示例演示了如何在MappingRedisConverter
中配置自定义RedisTypeMapper
:
例子 14.通过 Spring Java Config 配置一个自定义的RedisTypeMapper
class CustomRedisTypeMapper extends DefaultRedisTypeMapper {
//implement custom type mapping here
}
@Configuration
class SampleRedisConfiguration {
@Bean
public MappingRedisConverter redisConverter(RedisMappingContext mappingContext,
RedisCustomConversions customConversions, ReferenceResolver referenceResolver) {
MappingRedisConverter mappingRedisConverter = new MappingRedisConverter(mappingContext, null, referenceResolver,
customTypeMapper());
mappingRedisConverter.setCustomConversions(customConversions);
return mappingRedisConverter;
}
@Bean
public RedisTypeMapper customTypeMapper() {
return new CustomRedisTypeMapper();
}
}
7.3. Keyspaces
密钥空间定义用于为 Redis 哈希创建实际密钥的前缀。默认情况下,前缀设置为getClass().getName()
。您可以通过在聚合根级别上设置@RedisHash
或设置程序配置来更改此默认设置。但是,带 Comments 的键空间将取代任何其他配置。
以下示例显示了如何使用@EnableRedisRepositories
Comments 设置键空间配置:
例子 15.通过@EnableRedisRepositories
设置键空间
@Configuration
@EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class ApplicationConfig {
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
@Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
return Collections.singleton(new KeyspaceSettings(Person.class, "people"));
}
}
}
下面的示例演示如何以编程方式设置键空间:
例子 16.程序化键空间设置
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
@Bean
public RedisMappingContext keyValueMappingContext() {
return new RedisMappingContext(
new MappingConfiguration(
new MyKeyspaceConfiguration(), new IndexConfiguration()));
}
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
@Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
return Collections.singleton(new KeyspaceSettings(Person.class, "people"));
}
}
}
7.4. 次要 Metrics
Secondary indexes用于启用基于本地 Redis 结构的查找操作。每次保存时,值都会写入相应的索引,并在删除对象或expire时将其删除。
7.4.1. 简单属性索引
给定前面显示的示例Person
实体,我们可以通过用@Indexed
Comments 属性来为firstname
创建索引,如以下示例所示:
例子 17.Comments 驱动的索引
@RedisHash("people")
public class Person {
@Id String id;
@Indexed String firstname;
String lastname;
Address address;
}
为实际属性值构建索引。保存两个人(例如,“ rand”和“ aviendha”)会导致构建类似于以下内容的索引:
SADD people:firstname:rand e2c7dcee-b8cd-4424-883e-736ce564363e
SADD people:firstname:aviendha a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56
也可以在嵌套元素上具有索引。假设Address
具有city
属性,并用@Indexed
Comments。在这种情况下,一旦person.address.city
不是null
,我们将为每个城市设置集合,如以下示例所示:
SADD people:address.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e
此外,通过编程设置,您可以定义 Map 键和列表属性的索引,如以下示例所示:
@RedisHash("people")
public class Person {
// ... other properties omitted
Map<String,String> attributes; (1)
Map<String Person> relatives; (2)
List<Address> addresses; (3)
}
- (1)
SADD people:attributes.map-key:map-value e2c7dcee-b8cd-4424-883e-736ce564363e
- (2)
SADD people:relatives.map-key.firstname:tam e2c7dcee-b8cd-4424-883e-736ce564363e
- (3)
SADD people:addresses.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e
Warning
无法在References上解析索引。
与键空间一样,您可以配置索引而无需 Comments 实际的域类型,如以下示例所示:
例子 18.使用@EnableRedisRepositories 构建索引
@Configuration
@EnableRedisRepositories(indexConfiguration = MyIndexConfiguration.class)
public class ApplicationConfig {
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
public static class MyIndexConfiguration extends IndexConfiguration {
@Override
protected Iterable<IndexDefinition> initialConfiguration() {
return Collections.singleton(new SimpleIndexDefinition("people", "firstname"));
}
}
}
同样,与键空间一样,您可以以编程方式配置索引,如以下示例所示:
例子 19.程序化索引设置
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
@Bean
public RedisMappingContext keyValueMappingContext() {
return new RedisMappingContext(
new MappingConfiguration(
new KeyspaceConfiguration(), new MyIndexConfiguration()));
}
public static class MyIndexConfiguration extends IndexConfiguration {
@Override
protected Iterable<IndexDefinition> initialConfiguration() {
return Collections.singleton(new SimpleIndexDefinition("people", "firstname"));
}
}
}
7.4.2. 地理空间指数
假设Address
类型包含Point
类型的location
属性,该属性保存特定地址的地理坐标。通过使用@GeoIndexed
Comments 属性,Spring Data Redis 使用 Redis GEO
命令添加这些值,如以下示例所示:
@RedisHash("people")
public class Person {
Address address;
// ... other properties omitted
}
public class Address {
@GeoIndexed Point location;
// ... other properties omitted
}
public interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByAddressLocationNear(Point point, Distance distance); (1)
List<Person> findByAddressLocationWithin(Circle circle); (2)
}
Person rand = new Person("rand", "al'thor");
rand.setAddress(new Address(new Point(13.361389D, 38.115556D)));
repository.save(rand); (3)
repository.findByAddressLocationNear(new Point(15D, 37D), new Distance(200)); (4)
- (1) 使用
Point
和Distance
在嵌套属性上查询方法声明。 - (2) 嵌套属性上的查询方法声明,使用
Circle
在其中进行搜索。 - (3)
GEOADD people:address:location 13.361389 38.115556 e2c7dcee-b8cd-4424-883e-736ce564363e
- (4)
GEORADIUS people:address:location 15.0 37.0 200.0 km
在前面的示例中,经度和纬度值是通过使用GEOADD
来存储的,该GEOADD
使用对象的id
作为成员的名称。查找器方法允许使用Circle
或Point, Distance
组合查询这些值。
Note
不能将near
和within
与其他条件组合在一起。
7.5. 生存时间
Redis 中存储的对象可能仅在一定时间内有效。这对于在 Redis 中持久保存短寿命的对象特别有用,而无需在寿命到期时手动将其删除。可以使用@RedisHash(timeToLive=…)
以及使用KeyspaceSettings
(请参见Keyspaces)设置以秒为单位的到期时间。
可以通过在数字属性或方法上使用@TimeToLive
Comments 来设置更灵活的到期时间。但是,不要对同一类中的方法和属性都应用@TimeToLive
。以下示例显示了属性和方法上的@TimeToLive
注解:
例子 20.到期
public class TimeToLiveOnProperty {
@Id
private String id;
@TimeToLive
private Long expiration;
}
public class TimeToLiveOnMethod {
@Id
private String id;
@TimeToLive
public long getTimeToLive() {
return new Random().nextLong();
}
}
Note
使用@TimeToLive
显式 Comments 属性会从 Redis 读取实际的TTL
或PTTL
值。 -1 表示该对象没有关联的到期时间。
存储库实现可确保通过RedisMessageListenerContainer
订阅Redis 键空间通知。
当到期设置为正值时,将执行相应的EXPIRE
命令。除了保留原始副本外,幻影副本还将保留在 Redis 中,并设置为在原始副本后五分钟到期。这样做是为了使存储库支持能够发布RedisKeyExpiredEvent
,只要密钥过期,即使原始值已被删除,过期值也将在 Spring 的ApplicationEventPublisher
中保存。在使用 Spring Data Redis 存储库的所有已连接应用程序上都会收到到期事件。
默认情况下,初始化应用程序时禁用密钥过期侦听器。可以在@EnableRedisRepositories
或RedisKeyValueAdapter
中调整启动模式,以通过应用程序或在第一次插入带有 TTL 的实体时启动侦听器。有关可能的值,请参见EnableKeyspaceEvents。
RedisKeyExpiredEvent
包含过期的域对象以及密钥的副本。
Note
延迟或禁用到期事件侦听器的启动会影响RedisKeyExpiredEvent
发布。禁用的事件侦听器不会发布到期事件。由于延迟的侦听器初始化,延迟的启动可能会导致事件丢失。
Note
密钥空间通知消息侦听器会更改 Redis 中的notify-keyspace-events
设置(如果尚未设置)。现有设置不会被覆盖,因此您必须正确设置这些设置(或将其保留为空)。请注意,在 AWS ElastiCache 上禁用了CONFIG
,并且启用侦听器会导致错误。
Note
Redis 发布/订阅消息不是持久性的。如果在应用程序关闭时密钥过期,则不会处理过期事件,这可能导致二级索引包含对过期对象的引用。
7.6. 持续引用
用@Reference
标记属性允许存储简单的键引用,而不是将值复制到哈希本身中。从 Redis 加载后,引用将自动解析并 Map 回对象,如以下示例所示:
例子 21.samples 属性参考
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al'thor
mother = people:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 (1)
- (1) 引用存储引用对象的整个键(
keyspace:id
)。
Warning
保存引用对象时,引用对象不会保留。由于仅存储引用,因此您必须分别对引用的对象进行更改。无法解析在引用类型的属性上设置的索引。
7.7. 持续部分更新
在某些情况下,您无需加载和重写整个实体就可以在其中设置新值。最后一个活动时间的会话时间戳可能就是您要更改一个属性的情况。 PartialUpdate
可让您在现有对象上定义set
和delete
操作,同时注意更新实体本身和索引结构的潜在失效时间。以下示例显示了部分更新:
例子 22.samples 部分更新
PartialUpdate<Person> update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.set("firstname", "mat") (1)
.set("address.city", "emond's field") (2)
.del("age"); (3)
template.update(update);
update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.set("address", new Address("caemlyn", "andor")) (4)
.set("attributes", singletonMap("eye-color", "grey")); (5)
template.update(update);
update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.refreshTtl(true); (6)
.set("expiration", 1000);
template.update(update);
- (1) 将简单的
firstname
属性设置为mat
。 - (2) 将简单的'address.city'属性设置为'emond's field',而不必传入整个对象。注册自定义转换时,此功能不起作用。
- (3) 删除
age
属性。 - (4) 设置复杂的
address
属性。 - (5) 设置值的 Map,该 Map 将删除先前存在的 Map 并将值替换为给定的值。
- (6) 更改生存时间时自动更新服务器到期时间。
Note
更新复杂对象以及 Map(或其他集合)结构需要与 Redis 进行进一步交互以确定现有值,这意味着重写整个实体可能会更快。
7.8. 查询和查询方法
查询方法允许从方法名称自动派生简单查找程序查询,如以下示例所示:
例子 23.samples 库查找器方法
public interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByFirstname(String firstname);
}
Note
请确保设置查找程序方法中使用的属性以进行索引。
Note
Redis 存储库的查询方法仅支持对实体的查询以及带分页的实体集合。
使用派生的查询方法可能并不总是足以为要执行的查询建模。 RedisCallback
可以更好地控制索引结构甚至自定义索引的实际匹配。为此,请提供一个RedisCallback
返回单个或Iterable
一组id
值,如以下示例所示:
例子 24.使用 RedisCallback 的 samples 查找器
String user = //...
List<RedisSession> sessionsByUser = template.find(new RedisCallback<Set<byte[]>>() {
public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
return connection
.sMembers("sessions:securityContext.authentication.principal.username:" + user);
}}, RedisSession.class);
下表概述了 Redis 支持的关键字以及包含该关键字的方法的基本含义:
表 5.方法名称中支持的关键字
Keyword | Sample | Redis snippet |
---|---|---|
And | findByLastnameAndFirstname | SINTER …:firstname:rand …:lastname:al'thor |
Or | findByLastnameOrFirstname | SUNION …:firstname:rand …:lastname:al'thor |
Is,Equals | findByFirstname , findByFirstnameIs , findByFirstnameEquals | SINTER …:firstname:rand |
Top,First | findFirst10ByFirstname , findTop5ByFirstname |
7.9. 在集群上运行的 Redis 存储库
您可以在集群 Redis 环境中使用 Redis 存储库支持。有关ConnectionFactory
配置的详细信息,请参见“ Redis Cluster”部分。但是,还必须进行一些其他配置,因为默认的密钥分发会在整个群集及其插槽中扩展实体和二级索引。
下表显示了群集上数据的详细信息(基于先前的示例):
Key | Type | Slot | Node |
---|---|---|---|
people:e2c7dcee-b8cd-4424-883e-736ce564363e | 哈希 ID | 15171 | 127.0.0.1:7381 |
people:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 | 哈希 ID | 7373 | 127.0.0.1:7380 |
people:firstname:rand | index | 1700 | 127.0.0.1:7379 |
当所有涉及的键都 Map 到同一插槽时,某些命令(例如SINTER
和SUNION
)只能在服务器端处理。否则,必须在 Client 端进行计算。因此,将键空间固定到单个插槽非常有用,这样可以立即利用 Redis 服务器端计算。下表显示了您执行此操作时发生的情况(请注意插槽列中的更改以及节点列中的端口值):
Key | Type | Slot | Node |
---|---|---|---|
{people}:e2c7dcee-b8cd-4424-883e-736ce564363e | 哈希 ID | 2399 | 127.0.0.1:7379 |
{people}:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 | 哈希 ID | 2399 | 127.0.0.1:7379 |
{people}:firstname:rand | index | 2399 | 127.0.0.1:7379 |
Tip
使用 Redis 集群时,可以通过使用@RedisHash("{yourkeyspace}")
定义键空间并将其固定到特定插槽。
7.10. CDI 整合
存储库接口的实例通常由容器创建,在使用 Spring Data 时,Spring 是最自然的选择。 Spring 提供了用于创建 bean 实例的高级工具。 Spring Data Redis 附带了一个自定义 CDI 扩展,使您可以在 CDI 环境中使用存储库抽象。该扩展是 JAR 的一部分,因此要激活它,请将 Spring Data Redis JAR 放到您的 Classpath 中。
然后,您可以通过为RedisConnectionFactory
和RedisOperations
实现 CDI 生产者来设置基础结构,如以下示例所示:
class RedisOperationsProducer {
@Produces
RedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setHostName("localhost");
jedisConnectionFactory.setPort(6379);
jedisConnectionFactory.afterPropertiesSet();
return jedisConnectionFactory;
}
void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {
if (redisConnectionFactory instanceof DisposableBean) {
((DisposableBean) redisConnectionFactory).destroy();
}
}
@Produces
@ApplicationScoped
RedisOperations<byte[], byte[]> redisOperationsProducer(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
}
必要的设置可能会有所不同,具体取决于您的 JavaEE 环境。
每当容器请求存储库类型的 bean 时,Spring Data Redis CDI 扩展都会将所有可用的存储库作为 CDI bean 来获取,并为 Spring Data 存储库创建代理。因此,获取 Spring Data 存储库的实例只需声明@Injected
属性,如以下示例所示:
class RepositoryClient {
@Inject
PersonRepository repository;
public void businessMethod() {
List<Person> people = repository.findAll();
}
}
Redis 存储库需要RedisKeyValueAdapter
和RedisKeyValueTemplate
个实例。如果找不到提供的 bean,那么这些 bean 由 Spring Data CDI 扩展创建和 Management。但是,您可以提供自己的 bean 来配置RedisKeyValueAdapter
和RedisKeyValueTemplate
的特定属性。
7.11. Redis 存储库解剖
作为 Store 本身,Redis 提供了一个非常狭窄的低级 API,而将较高级的功能(如二级索引和查询操作)留给了用户。
本节提供了由存储库抽象发出的命令的更详细视图,以更好地了解潜在的性能影响。
将以下实体类视为所有操作的起点:
例子 25.例子实体
@RedisHash("people")
public class Person {
@Id String id;
@Indexed String firstname;
String lastname;
Address hometown;
}
public class Address {
@GeoIndexed Point location;
}
7.11.1. 插入新的
repository.save(new Person("rand", "al'thor"));
HMSET "people:19315449-cda2-4f5c-b696-9cb8018fa1f9" "_class" "Person" "id" "19315449-cda2-4f5c-b696-9cb8018fa1f9" "firstname" "rand" "lastname" "al'thor" (1)
SADD "people" "19315449-cda2-4f5c-b696-9cb8018fa1f9" (2)
SADD "people:firstname:rand" "19315449-cda2-4f5c-b696-9cb8018fa1f9" (3)
SADD "people:19315449-cda2-4f5c-b696-9cb8018fa1f9:idx" "people:firstname:rand" (4)
- (1) 将平展的条目另存为哈希。
- (2) 将\ <1>中编写的哈希键添加到同一键空间中的实体的辅助索引中。
- (3) 将\ <2>中编写的哈希键添加到具有属性值的名字的二级索引中。
- (4) 将\ <3>的索引添加到帮助程序结构集中以供 Importing,以跟踪删除/更新时要清理的索引。
7.11.2. 替换现有的
repository.save(new Person("e82908cf-e7d3-47c2-9eec-b4e0967ad0c9", "Dragon Reborn", "al'thor"));
DEL "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (1)
HMSET "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" "_class" "Person" "id" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" "firstname" "Dragon Reborn" "lastname" "al'thor" (2)
SADD "people" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (3)
SMEMBERS "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx" (4)
TYPE "people:firstname:rand" (5)
SREM "people:firstname:rand" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (6)
DEL "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx" (7)
SADD "people:firstname:Dragon Reborn" "e82908cf-e7d3-47c2-9eec-b4e0967ad0c9" (8)
SADD "people:e82908cf-e7d3-47c2-9eec-b4e0967ad0c9:idx" "people:firstname:Dragon Reborn" (9)
- (1) 删除现有的散列,以避免可能不再存在的散列键剩余。
- (2) 将扁平化条目保存为哈希。
- (3) 将\ <1>中编写的哈希键添加到同一键空间中的实体的辅助索引中。
- (4) 获取可能需要更新的现有索引结构。
- (5) 检查索引是否存在及其类型(文本,geo 等)。
- (6) 从索引中删除潜在的现有键。
- (7) 删除帮助程序持有索引信息。
- (8) 将\ <2>中添加的哈希键添加到具有属性值的名字的二级索引中。
- (9) 将\ <6>的索引添加到帮助程序结构集中以供 Importing,以跟踪删除/更新时要清理的索引。
7.11.3. 保存地理数据
地理索引遵循与基于普通文本的规则相同的规则,但是使用地理结构来存储值。保存使用地理索引属性的实体将导致以下命令:
GEOADD "people:hometown:location" "13.361389" "38.115556" "76900e94-b057-44bc-abcf-8126d51a621b" (1)
SADD "people:76900e94-b057-44bc-abcf-8126d51a621b:idx" "people:hometown:location" (2)
- (1) 将保存的条目的键添加到 geo 索引。
- (2) 跟踪索引结构。
7.11.4. 使用简单索引查找
repository.findByFirstname("egwene");
SINTER "people:firstname:egwene" (1)
HGETALL "people:d70091b5-0b9a-4c0a-9551-519e61bc9ef3" (2)
HGETALL ...
- (1) 提取二级索引中包含的键。
- (2) 分别获取\ <1>返回的每个键。
7.11.5. 使用地理位置索引查找
repository.findByHometownLocationNear(new Point(15, 37), new Distance(200, KILOMETERS));
GEORADIUS "people:hometown:location" "15.0" "37.0" "200.0" "km" (1)
HGETALL "people:76900e94-b057-44bc-abcf-8126d51a621b" (2)
HGETALL ...
- (1) 提取二级索引中包含的键。
- (2) 分别获取\ <1>返回的每个键。
Appendixes
附录文件结构
附录包含各种其他详细信息,它们补充了参考文档其余部分中的信息:
-
“ Schema”定义了 Spring Data Redis 提供的模式。
-
“ Command Reference”详细说明
RedisTemplate
支持哪些命令。
附录 A:架构
Spring Data Redis Schema(重新命名空间)
附录 B:命令参考
Supported Commands
表 6. RedisTemplate
支持的 Redis 命令
Command | Template Support |
---|---|
APPEND | X |
AUTH | X |
BGREWRITEAOF | X |
BGSAVE | X |
BITCOUNT | X |
BITOP | X |
BLPOP | X |
BRPOP | X |
BRPOPLPUSH | X |
CLIENT KILL | X |
CLIENT GETNAME | X |
CLIENT LIST | X |
CLIENT SETNAME | X |
CLUSTER SLOTS | - |
COMMAND | - |
COMMAND COUNT | - |
COMMAND GETKEYS | - |
COMMAND INFO | - |
CONFIG GET | X |
CONFIG RESETSTAT | X |
CONFIG REWRITE | - |
CONFIG SET | X |
DBSIZE | X |
DEBUG OBJECT | - |
DEBUG SEGFAULT | - |
DECR | X |
DECRBY | X |
DEL | X |
DISCARD | X |
DUMP | X |
ECHO | X |
EVAL | X |
EVALSHA | X |
EXEC | X |
EXISTS | X |
EXPIRE | X |
EXPIREAT | X |
FLUSHALL | X |
FLUSHDB | X |
GET | X |
GETBIT | X |
GETRANGE | X |
GETSET | X |
HDEL | X |
HEXISTS | X |
HGET | X |
HGETALL | X |
HINCRBY | X |
HINCRBYFLOAT | X |
HKEYS | X |
HLEN | X |
HMGET | X |
HMSET | X |
HSCAN | X |
HSET | X |
HSETNX | X |
HVALS | X |
INCR | X |
INCRBY | X |
INCRBYFLOAT | X |
INFO | X |
KEYS | X |
LASTSAVE | X |
LINDEX | X |
LINSERT | X |
LLEN | X |
LPOP | X |
LPUSH | X |
LPUSHX | X |
LRANGE | X |
LREM | X |
LSET | X |
LTRIM | X |
MGET | X |
MIGRATE | - |
MONITOR | - |
MOVE | X |
MSET | X |
MSETNX | X |
MULTI | X |
OBJECT | - |
PERSIST | X |
PEXIPRE | X |
PEXPIREAT | X |
PFADD | X |
PFCOUNT | X |
PFMERGE | X |
PING | X |
PSETEX | X |
PSUBSCRIBE | X |
PTTL | X |
PUBLISH | X |
PUBSUB | - |
PUBSUBSCRIBE | - |
QUIT | X |
RANDOMKEY | X |
RENAME | X |
RENAMENX | X |
RESTORE | X |
ROLE | - |
RPOP | X |
RPOPLPUSH | X |
RPUSH | X |
RPUSHX | X |
SADD | X |
SAVE | X |
SCAN | X |
SCARD | X |
SCRIPT EXITS | X |
SCRIPT FLUSH | X |
SCRIPT KILL | X |
SCRIPT LOAD | X |
SDIFF | X |
SDIFFSTORE | X |
SELECT | X |
SENTINEL FAILOVER | X |
SENTINEL GET-MASTER-ADD-BY-NAME | - |
SENTINEL MASTER | - |
SENTINEL MASTERS | X |
SENTINEL MONITOR | X |
SENTINEL REMOVE | X |
SENTINEL RESET | - |
SENTINEL SET | - |
SENTINEL SLAVES | X |
SET | X |
SETBIT | X |
SETEX | X |
SETNX | X |
SETRANGE | X |
SHUTDOWN | X |
SINTER | X |
SINTERSTORE | X |
SISMEMBER | X |
SLAVEOF | X |
SLOWLOG | - |
SMEMBERS | X |
SMOVE | X |
SORT | X |
SPOP | X |
SRANDMEMBER | X |
SREM | X |
SSCAN | X |
STRLEN | X |
SUBSCRIBE | X |
SUNION | X |
SUNIONSTORE | X |
SYNC | - |
TIME | X |
TTL | X |
TYPE | X |
UNSUBSCRIBE | X |
UNWATCH | X |
WATCH | X |
ZADD | X |
ZCARD | X |
ZCOUNT | X |
ZINCRBY | X |
ZINTERSTORE | X |
ZLEXCOUNT | - |
ZRANGE | X |
ZRANGEBYLEX | - |
ZREVRANGEBYLEX | - |
ZRANGEBYSCORE | X |
ZRANK | X |
ZREM | X |
ZREMRANGEBYLEX | - |
ZREMRANGEBYRANK | X |
ZREVRANGE | X |
ZREVRANGEBYSCORE | X |
ZREVRANK | X |
ZSCAN | X |
ZSCORE | X |
ZUNINONSTORE | X |