Spring Data Redis简介参考文献附录

原文:https://www.docs4dev.com/docs/zh/spring-data-redis/2.1.5.RELEASE/reference/all.html

前言

Spring Data Redis项目通过使用键值样式数据存储将核心Spring概念应用于解决方案的开发。我们提供“模板”作为发送和接收消息的高级抽象。您可能会注意到Spring Framework中与JDBC支持的相似之处。

1. 新功能

本节简要介绍最新版本中新增和值得注意的项目。

1.1. Spring Data Redis 2.1中的新功能

1.2. Spring Data Redis 2.0中的新功能

1.3. Spring Data Redis 1.8中的新功能

1.4. Spring Data Redis 1.7中的新功能

1.5. Spring Data Redis 1.6中的新功能

1.6. Spring Data Redis 1.5中的新功能

基于

本文档是Spring Data Redis(SDR)支持的参考指南。它解释了键值模块的概念和语义以及各种 Store 命名空间的语法。

有关键值存储,Spring或Spring Data示例的介绍,请参阅 Getting Started 。本文档仅涉及Spring Data Redis支持,并假定用户熟悉键值存储和Spring概念。

2. 为什么Spring Data Redis?

Spring Framework是领先的全栈Java / JEE应用程序框架。它提供了一个轻量级容器和一个非侵入式编程模型,它通过使用依赖注入,AOP和便携式服务抽象来实现。

NoSQL 存储系统为经典RDBMS提供了水平可扩展性和速度的替代方案。在实现方面,键值存储代表NoSQL空间中最大(和最老)的成员之一。

Spring Data Redis(SDR)框架通过Spring的优秀基础架构支持消除了与 Store 交互所需的冗余任务和样板代码,可以轻松编写使用Redis键值存储的Spring应用程序。

3. 要求

Spring Data Redis 2.x二进制文件需要JDK级别8.0及以上版本以及 Spring Framework 5.1.5.RELEASE及以上版本。

在键值存储方面,需要 Redis 2.6.x或更高。 Spring Data Redis目前已针对最新的4.0版本进行了测试。

4. 入门

本节提供了一个易于遵循的Spring Data Redis模块入门指南。

4.1. 第一步

正如 Why Spring Data Redis? 中所解释的,Spring Data Redis(SDR)提供了Spring框架和Redis键值存储之间的集成。因此,您应该熟悉这两个框架。在整个SDR文档中,每个部分都提供了相关资源的链接。但是,在阅读本指南之前,您应该熟悉这些主题。

4.1.1. 学习 Spring

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和Key Value Store

NoSQL Store 风靡了存储世界。这是一个涉及众多解决方案,术语和模式的庞大领域(更糟糕的是,甚至术语本身也有多个 meanings )。虽然一些原则很常见,但在某种程度上熟悉SDR支持的 Store 至关重要。熟悉这些解决方案的最佳方式是阅读他们的文档并按照他们的示例进行操作。它通常不需要花费五到十分钟来完成它们,如果你来自RDMBS背景,很多时候这些练习可以让人大开眼界。

4.1.3. 试用示例

可以在 http://github.com/spring-projects/spring-data-keyvalue-examples 的专用Spring Data示例仓库中找到键值存储的各种示例。对于Spring Data Redis,您应该特别注意 retwisj 示例,这是一个构建在Redis之上的Twitter克隆,可以在本地运行或部署到 Cloud 中。有关详细信息,请参阅 documentation ,以下博客 entry

4.2. 需要帮助?

如果您遇到问题或者您只是在寻找建议,请使用以下链接之一:

4.2.1. 社区支持

Stack Overflow 上的Spring Data标记是所有Spring Data(不仅仅是Redis)用户共享信息并互相帮助的留言板。请注意,需要注册 only 才能发布。

4.2.2. 专业支持

专业的,源头支持,保证响应时间,可从 Pivotal Software, Inc. ,Spring Data和Spring背后的公司获得。

4.3. 以下开发

有关Spring Data源代码存储库,夜间构建和快照构件的信息,请参阅Spring Data主页 page

通过在 spring-dataspring-data-redis 上与Stack Overflow上的开发人员交互,您可以帮助Spring Data最好地满足Spring社区的需求。

如果您遇到错误或想要建议改进(包括本文档),请在Spring Data问题 tracker 上创建一个票证。

要及时了解Spring eco系统中的最新新闻和公告,请订阅Spring社区 Portal

最后,您可以在Twitter上关注Spring blog 或项目团队( @SpringData )。

文件结构

这部分参考文档解释了Spring Data Redis提供的核心功能。

Redis support 介绍了Redis模块功能集。

5. Redis支持

Spring Data支持的其中一个键值存储是 Redis 。引用Redis项目主页:

Redis是一个高级键值存储。它与memcached类似,但数据集不是易失性的,值可以是字符串,与memcached完全相同,但也可以是列表,集合和有序集。所有这些数据类型都可以通过原子操作来操作,以推送/弹出元素,添加/删除元素,执行服务器端并集,交集,集合之间的差异等等。 Redis支持不同类型的排序功能。

Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能。它提供了与 Store 交互的低级和高级抽象,使用户免于基础设施问题。

5.1. Redis要求

Spring Redis需要Redis 2.6或更高版本,Spring Data Redis与 LettuceJedis 集成,这是Redis的两个流行的开源Java库。

5.2. Redis支持高级视图

Redis支持提供了几个组件。对于大多数任务,高级抽象和支持服务是最佳选择。请注意,在任何时候,您都可以在图层之间移动。例如,您可以获得与Redis直接通信的低级连接(甚至是本机库)。

5.3. 正在连接到Redis

使用Redis和Spring时的首要任务之一是通过IoC容器连接到 Store 。为此,需要Java连接器(或绑定)。无论您选择哪个库,都只需要使用一组Spring Data Redis API(在所有连接器中表现一致): org.springframework.data.redis.connection 包及其 RedisConnectionRedisConnectionFactory 接口,用于处理和检索活动连接Redis的。

5.3.1. RedisConnection和RedisConnectionFactory

RedisConnection 为Redis通信提供核心构建块,因为它处理与Redis后端的通信。它还会自动将底层连接库异常转换为Spring的一致DAO异常 hierarchy ,这样您就可以在没有任何代码更改的情况下切换连接器,因为操作语义保持不变。

对于需要本机库API的极端情况, RedisConnection 提供了一个专用方法( getNativeConnection ),它返回用于通信的原始底层对象。

通过 RedisConnectionFactory 创建活动 RedisConnection 对象。此外,工厂充当 PersistenceExceptionTranslator 对象,这意味着,一旦声明,它们就会让您进行透明的异常转换。例如,您可以通过使用 @Repository 注释和AOP进行异常转换。有关更多信息,请参阅Spring Framework文档中的专用 section

根据基础配置,工厂可以返回新连接或现有连接(使用池或共享本机连接时)。

使用 RedisConnectionFactory 的最简单方法是通过IoC容器配置相应的连接器并将其注入using类。

遗憾的是,目前并非所有连接器都支持所有Redis功能。在基础库不支持的Connection API上调用方法时,会抛出 UnsupportedOperationException

5.3.2. 配置 Lettuce 连接器

Lettuce 是一个基于 Netty 的开源连接器,由Spring Data Redis通过 org.springframework.data.redis.connection.lettuce 包支持。以下示例显示如何创建新的Lettuce连接工厂:

@Configuration
class AppConfig {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
  }
}

还有一些可以调整的特定 Lettuce 特定连接参数。默认情况下, LettuceConnectionFactory 创建的所有 LettuceConnection 实例共享所有非阻塞和非事务操作的相同线程安全本机连接。要每次使用专用连接,请将 shareNativeConnection 设置为 false 。如果 shareNativeConnection 设置为 falseLettuceConnectionFactory 也可以配置为使用 LettucePool 汇集阻塞和事务连接或所有连接。

Lettuce与Netty的 native transports 集成,让你使用Unix域套接字与Redis进行通信。确保包含与运行时环境匹配的相应本机传输依赖项。以下示例显示如何在 /var/run/redis.sock 为Unix域套接字创建Lettuce连接工厂:

@Configuration
class AppConfig {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    return new LettuceConnectionFactory(new RedisSocketConfiguration("/var/run/redis.sock"));
  }
}

Netty目前支持用于OS本地传输的epoll(Linux)和kqueue(BSD / macOS)接口。

5.3.3. 配置Jedis连接器

Jedis 是一个社区驱动的连接器,由Spring Data Redis模块通过 org.springframework.data.redis.connection.jedis 包支持。在最简单的形式中,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);
  }
}

5.3.4. 写入Master,从Replica读取

Redis主站/副本设置 - 没有自动故障转移(用于自动故障转移,请参阅: Sentinel ) - 不仅允许将数据安全地存储在更多节点上。它还允许通过使用 Lettuce 从副本中读取数据,同时将写入推送到主服务器。您可以使用 LettuceClientConfiguration 设置要使用的读/写策略,如以下示例所示:

@Configuration
class WriteToMasterReadFromReplicaConfiguration {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
      .readFrom(SLAVE_PREFERRED)
      .build();

    RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379);

    return new LettuceConnectionFactory(serverConfig, clientConfig);
  }
}

对于通过 INFO 命令报告非公共地址的环境(例如,使用AWS时),请使用 RedisStaticMasterReplicaConfiguration 而不是 RedisStandaloneConfiguration

5.4. Redis Sentinel支持

为了处理高可用性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);
}

RedisSentinelConfiguration 也可以使用 PropertySource 定义,它允许您设置以下属性:

配置属性

有时,需要与其中一个Sentinels直接交互。使用 RedisConnectionFactory.getSentinelConnection()RedisConnection.getSentinelCommands() 可以访问配置的第一个活动Sentinel。

5.5. 通过RedisTemplate处理对象

大多数用户可能会使用 RedisTemplate 及其相应的包 org.springframework.data.redis.core 。事实上,该模板是Redis模块的核心类,因为它具有丰富的功能集。该模板为Redis交互提供了高级抽象。虽然 RedisConnection 提供了接受和返回二进制值( byte 数组)的低级方法,但该模板负责序列化和连接管理,使用户无需处理此类详细信息。

此外,模板提供操作视图(在Redis命令 reference 分组之后),提供丰富的,通用的接口,用于处理特定类型或某些键(通过 KeyBound 接口)如下表所述:

接口说明
键类型操作
GeoOperationsRedis地理空间操作,例如 GEOADDGEORADIUS ,...
HashOperationsRedis哈希操作
HyperLogLogOperationsRedis HyperLogLog操作,例如 PFADDPFCOUNT ,......
ListOperationsRedis列表操作
SetOperationsRedis设置操作
ValueOperationsRedis字符串(或值)操作
ZSetOperationsRedis zset(或有序集)操作
键绑定操作
BoundGeoOperationsRedis键绑定地理空间操作
BoundHashOperationsRedis哈希键绑定操作
BoundKeyOperationsRedis键绑定操作
BoundListOperationsRedis列表键绑定操作
BoundSetOperationsRedis设置键绑定操作
BoundValueOperationsRedis字符串(或值)键绑定操作
BoundZSetOperationsRedis zset(或有序集)键绑定操作

配置完成后,模板是线程安全的,可以跨多个实例重用。

RedisTemplate 在其大多数操作中使用基于Java的序列化程序。这意味着模板编写或读取的任何对象都通过Java进行序列化和反序列化。您可以更改模板上的序列化机制,Redis模块提供了多个实现,这些实现可在 org.springframework.data.redis.serializer 包中找到。有关更多信息,请参见 Serializers 。您还可以将任何序列化程序设置为null,并通过将 enableDefaultSerializer 属性设置为 false 将RedisTemplate与原始字节数组一起使用。请注意,模板要求所有键都为非null。但是,只要底层序列化程序接受它们,值就可以为null。阅读每个序列化程序的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模块为 RedisConnectionRedisTemplate 提供了两个扩展,分别为 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模板一样, RedisTemplateStringRedisTemplate 允许您通过 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. 序列化程序

从框架的角度来看,存储在Redis中的数据只是字节数。虽然Redis本身支持各种类型,但在大多数情况下,这些类型指的是数据的存储方式而不是它所代表的方式。由用户决定信息是否被翻译成字符串或任何其他对象。

在Spring Data中,用户(自定义)类型和原始数据之间的转换(反之亦然)在 org.springframework.data.redis.serializer 包中处理Redis。

该软件包包含两种类型的序列化程序,顾名思义,它们负责序列化过程:

这些变体之间的主要区别在于 RedisSerializer 主要序列化为 byte[] 而读者和作者使用 ByteBuffer

可以使用多种实现(包括本文档中已经提到的两种实现):

但是,可以通过Spring OXM 支持使用 OxmSerializer 进行对象/ XML映射,或者使用 Jackson2JsonRedisSerializerGenericJackson2JsonRedisSerializerJSON 格式存储数据。

请注意,存储格式不仅限于值。它可以用于键,值或哈希,没有任何限制。

默认情况下, RedisCacheRedisTemplate 配置为使用Java本机序列化。众所周知,Java本机序列化允许由利用易受攻击的库和类注入未经验证的字节码的有效负载引起的远程代码执行。在反序列化期间,操作输入可能导致应用程序中不需要的代码执行步。因此,请勿在不受信任的环境中使用序列化。通常,我们强烈建议使用任何其他消息格式(例如JSON)。

如果您担心Java序列化导致的安全漏洞,请考虑核心JVM级别的通用序列化过滤机制,最初为JDK 9开发但向后移植到JDK 8,7和6:

5.8. 哈希映射

可以使用Redis中的各种数据结构存储数据。 Jackson2JsonRedisSerializer 可以转换 JSON 格式的对象。理想情况下,可以使用普通键将JSON存储为值。您可以使用Redis哈希实现更复杂的结构化对象映射。 Spring Data Redis提供了各种将数据映射到哈希的策略(取决于用例):

5.8.1. 哈希映射器

哈希映射器是将 Map 对象转换为 Map<K, V> 并返回。 HashMapper 适用于Redis Hashes。

有多种实现方式:

以下示例显示了实现哈希映射的一种方法:

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 Hash映射。 Jackson2HashMapper 可以将顶级属性映射为哈希字段名称,并可选择展平结构。简单类型映射到简单值。复杂类型(嵌套对象,集合,映射等)表示为嵌套JSON。

展平为所有嵌套属性创建单独的哈希条目,并尽可能将复杂类型解析为简单类型。

考虑以下类及其包含的数据结构:

public class Person {
  String firstname;
  String lastname;
  Address address;
}

public class Address {
  String city;
  String country;
}

下表显示了前一类中的数据如何在法线贴图中显示:

哈希字段
名字Jon
姓氏Snow
地址{ "city" : "Castle Black", "country" : "The North" }

下表显示了前一类中的数据如何在平面映射中显示:

哈希字段
名字Jon
姓氏Snow
address.cityCastle Black
address.countryThe North

展平要求所有属性名称不会干扰JSON路径。使用展平时,不支持在 Map 键中使用点或括号或作为属性名称。生成的哈希不能映射回Object。

5.9. Redis消息(Pub / Sub)

Spring Data为Redis提供了专用的消息传递集成,功能类似,并命名为Spring Framework中的JMS集成。

Redis消息传递大致可分为两个功能区域:

这是通常称为Publish / Subscribe(简称Pub / Sub)的模式示例。 RedisTemplate 类用于生成消息。对于类似于Java EE的消息驱动bean样式的异步接收,Spring Data提供了一个专用的消息监听器容器,用于创建消息驱动的POJO(MDP),以及用于同步接收的 RedisConnection Contract 。

org.springframework.data.redis.connectionorg.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. 订阅(接收邮件)

在接收方,可以通过直接命名或使用模式匹配来订阅一个或多个通道。后一种方法非常有用,因为它不仅可以使用一个命令创建多个订阅,还可以监听尚未在订阅时创建的通道(只要它们与模式匹配)。

在低级别, RedisConnection 提供 subscribepSubscribe 方法,分别映射Redis命令以按通道或按模式进行订阅。请注意,可以使用多个通道或模式作为参数。要更改连接的订阅或查询是否正在侦听, RedisConnection 提供了 getSubscriptionisSubscribed 方法。

Spring Data Redis中的订阅命令正在阻止。也就是说,在连接上调用subscribe会导致当前线程被阻塞因为它开始等待消息。只有在取消订阅时才会释放该线程,这在另一个线程在 same 连接上调用 unsubscribepUnsubscribe 时发生。有关此问题的解决方案,请参阅“ Message Listener Containers ”(本文档后面部分)。

如前所述,一旦订阅,连接就开始等待消息。仅允许添加新订阅,修改现有订阅和取消现有订阅的命令。调用 subscribepSubscribeunsubscribepUnsubscribe 以外的任何内容都会引发异常。

为了订阅消息,需要实现 MessageListener 回调。每次新消息到达时,都会调用回调,并且 onMessage 方法会运行用户代码。该接口不仅可以访问实际消息,还可以访问通过它接收的通道以及订阅用于匹配通道的模式(如果有)。此信息使被叫方不仅可以通过内容区分各种消息,还可以检查其他详细信息。

消息侦听器容器

由于其阻塞性质,低级订阅不具吸引力,因为它需要每个单个侦听器的连接和线程管理。为了缓解这个问题,Spring Data提供了 RedisMessageListenerContainer ,它完成了所有繁重的工作。如果您熟悉EJB和JMS,那么您应该找到熟悉的概念,因为它的设计尽可能接近Spring Framework及其消息驱动的POJO(MDP)中的支持。

RedisMessageListenerContainer 充当消息侦听器容器。它用于从Redis通道接收消息并驱动注入其中的 MessageListener 实例。侦听器容器负责消息接收的所有线程并将其分派到侦听器中进行处理。消息监听器容器是MDP和消息传递提供者之间的中介,并负责注册以接收消息,资源获取和释放,异常转换等。这使您作为应用程序开发人员可以编写与接收消息(并对其做出反应)相关联的(可能是复杂的)业务逻辑,并将样板Redis基础结构关注委托给框架。

此外,为了最小化应用程序占用空间, RedisMessageListenerContainer 允许多个侦听器共享一个连接和一个线程,即使它们不共享订阅。因此,无论应用程序跟踪多少个侦听器或通道,运行时成本在其整个生命周期内保持不变。此外,容器允许更改运行时配置,以便您可以在应用程序运行时添加或删除侦听器,而无需重新启动。此外,容器使用延迟订阅方法,仅在需要时使用 RedisConnection 。如果所有侦听器都已取消订阅,则会自动执行清理,并释放该线程。

为了帮助消息的异步性,容器需要 java.util.concurrent.Executor (或Spring的 TaskExecutor )来分派消息。根据负载,侦听器数量或运行时环境,您应该更改或调整执行程序以更好地满足您的需求。特别是,在托管环境(例如应用程序服务器)中,强烈建议选择适当的 TaskExecutor 以利用其运行时。

MessageListenerAdapter

MessageListenerAdapter 类是Spring异步消息传递支持的最后一个组件。简而言之,它允许您将几乎 any 类暴露为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 类型的 contents 强类型化的。此外,发送消息的通道或模式可以作为 String 类型的第二个参数传递给方法:

public class DefaultMessageDelegate implements MessageDelegate {
  // implementation elided for clarity...
}

注意 MessageDelegate 接口(上面的 DefaultMessageDelegate 类)的上述实现如何具有 no 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>

侦听器主题可以是通道(例如, 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 )。由方法调用引起的任何异常都由容器捕获并处理(默认情况下,会记录异常)。

5.10. Redis Transaction

Redis通过 multiexecdiscard 命令为 transactions 提供支持。这些操作可在 RedisTemplate 上找到。但是, RedisTemplate 不保证在具有相同连接的事务中执行所有操作。

Spring Data Redis提供 SessionCallback 接口,以便在需要使用相同的 connection 执行多个操作时使用,例如使用Redis事务时。以下示例使用 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 方法可以让您为事务结果传递自定义序列化程序。

从版本1.1开始,对 RedisConnectionRedisTemplateexec 方法进行了重要更改。以前,这些方法直接从连接器返回事务的结果。这意味着数据类型通常与 RedisConnection 方法返回的数据类型不同。例如, zAdd 返回一个布尔值,指示元素是否已添加到有序集。大多数连接器将此值作为long返回,Spring Data Redis执行转换。另一个常见的区别是大多数连接器返回状态回复(通常是字符串, OK ),用于 set 等操作。 Spring Data Redis通常会丢弃这些回复。在1.1之前,这些转换未在 exec 的结果上执行。此外,结果未在 RedisTemplate 中反序列化,因此它们通常包含原始字节数组。如果此更改破坏了您的应用程序,请在 RedisConnectionFactory 上将 convertPipelineAndTxResults 设置为 false 以禁用此行为。

5.10.1. @Transactional支持

默认情况下,事务支持已禁用,必须通过设置 setEnableTransactionSupport(true) 为每个正在使用的 RedisTemplate 显式启用。这样做会强制将当前 RedisConnection 绑定到触发 MULTI 的当前 Thread 。如果事务完成且没有错误,则调用 EXEC 。否则 DISCARD 被调用。进入 MULTI 后, RedisConnection 队列写入操作。所有 readonly 操作(例如 KEYS )都通过管道传输到新的(非线程绑定的) RedisConnection

以下示例显示了如何配置事务管理:

示例1.启用事务管理的配置

@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
  }

  @Bean
  public PlatformTransactionManager transactionManager() throws SQLException {
    return new DataSourceTransactionManager(dataSource());   (3)
  }

  @Bean
  public DataSource dataSource() throws SQLException {
    // ...
  }
}
1配置Spring上下文以启用 declarative transaction management
2配置 RedisTemplate 通过绑定到当前线程的连接来参与事务。
3事务管理需要 PlatformTransactionManager 。 Spring Data Redis未附带 PlatformTransactionManager 实现。假设您的应用程序使用JDBC,Spring Data Redis可以使用现有的事务管理器参与事务。

以下示例均演示了使用限制:

示例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. 流水线

Redis为 pipelining 提供支持,它涉及向服务器发送多个命令而无需等待回复,然后在一个步骤中读取回复。当您需要连续发送多个命令时,流水线操作可以提高性能,例如向同一个List添加许多元素。

Spring Data Redis提供了几种用于在管道中执行命令的 RedisTemplate 方法。如果您不关心流水线操作的结果,可以使用标准 execute 方法,为 pipeline 参数传递 trueexecutePipelined 方法在管道中运行提供的 RedisCallbackSessionCallback 并返回结果,如以下示例所示:

//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,因为此值将被丢弃,以支持返回流水线命令的结果。

从版本1.1开始,对 RedisConnectionRedisTemplateexec 方法进行了重要更改。以前,这些方法直接从连接器返回事务的结果。这意味着数据类型通常与 RedisConnection 方法返回的数据类型不同。例如, zAdd 返回一个布尔值,指示元素是否已添加到有序集合中。大多数连接器将此值作为long返回,Spring Data Redis执行转换。另一个常见的区别是大多数连接器为 set 等操作返回状态答复(通常是字符串, OK )。这些回复是通常由Spring Data Redis丢弃。在1.1之前,这些转换未在 exec 的结果上执行。此外,结果未在 RedisTemplate 中反序列化,因此它们通常包含原始字节数组。如果此更改破坏了您的应用程序,请在 RedisConnectionFactory 上将 convertPipelineAndTxResults 设置为 false 以禁用此行为。

5.12. Redis脚本

Redis 2.6及更高版本通过 evalevalsha 命令为执行Lua脚本提供支持。 Spring Data Redis为脚本执行提供高级抽象,处理序列化并自动使用Redis脚本缓存。

可以通过调用 RedisTemplateReactiveRedisTemplateexecute 方法来运行脚本。两者都使用可配置的 ScriptExecutor (或 ReactiveScriptExecutor )来运行提供的脚本。默认情况下, ScriptExecutor (或 ReactiveScriptExecutor )负责序列化提供的键和参数以及反序列化脚本结果。这是通过模板的键和值序列化程序完成的。还有一个额外的重载,允许您为脚本参数和结果传递自定义序列化程序。

默认 ScriptExecutor 通过检索脚本的SHA1并尝试首先运行 evalsha 来优化性能,如果Redis脚本缓存中尚不存在脚本,则返回 eval

以下示例使用Lua脚本运行常见的“检查和设置”方案。这是Redis脚本的理想用例,因为它要求以原子方式运行一组命令,并且一个命令的行为受另一个命令的结果的影响。

@Bean
public RedisScript<Boolean> script() {

  ScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua");
  return RedisScript.of(scriptSource, Boolean.class);
}
public class Example {

  @Autowired
  RedisScript<Boolean> script;

  public boolean checkAndSet(String expectedValue, String newValue) {
    return redisTemplate.execute(script, singletonList("key"), asList(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 应该是 LongBooleanList 或反序列化值类型之一。如果脚本返回一个丢弃状态(具体地说, OK ),它也可以是 null

最好在应用程序上下文中配置 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密钥增量,而集合允许以最小的存储空间或API泄漏轻松管理Redis密钥。特别是, RedisSetRedisZSet 接口可以轻松访问Redis支持的设置操作,例如 intersectionunionRedisList 在Redis之上实现 ListQueueDeque 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 Abstraction

在2.0中更改

Spring Redis通过 org.springframework.data.redis.cache 包为Spring cache abstraction 提供了一个实现。要将Redis用作支持实现,请将 RedisCacheManager 添加到您的配置中,如下所示:

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
	return RedisCacheManager.create(connectionFactory);
}

RedisCacheManager 行为可以使用 RedisCacheManagerBuilder 进行配置,允许您设置默认的 RedisCacheConfiguration ,事务行为和预定义的高速缓存。

RedisCacheManager cm = RedisCacheManager.builder(connectionFactory)
	.cacheDefaults(defaultCacheConfig())
	.withInitialCacheConfigurations(singletonMap("predefined", defaultCacheConfig().disableCachingNullValues()))
	.transactionAware()
	.build();

如前面的示例所示, RedisCacheManager 允许基于每个缓存定义配置。

使用 RedisCacheManager 创建的 RedisCache 的行为是使用 RedisCacheConfiguration 定义的。通过该配置,您可以设置密钥到期时间,前缀和 RedisSerializer 实现,以便与二进制存储格式进行转换,如以下示例所示:

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofSeconds(1))
	.disableCachingNullValues();

RedisCacheManager 默认为无锁 RedisCacheWriter ,用于读取和写入二进制值。无锁缓存可提高吞吐量。缺少条目锁定可能导致 putIfAbsentclean 方法的重叠非原子命令,因为这些命令需要发送多个命令Redis的。锁定对应物通过设置显式锁定键并检查是否存在此键来防止命令重叠,这会导致其他请求和潜在的命令等待时间。

可以按如下方式选择锁定行为:

RedisCacheManager cm = RedisCacheManager.build(RedisCacheWriter.lockingRedisCacheWriter())
	.cacheDefaults(defaultCacheConfig())
	...

默认情况下,缓存条目的任何 key 都以实际缓存名称为后缀,后跟两个冒号。此行为可以更改为静态和计算前缀。

以下示例显示如何设置静态前缀:

// static key prefix
RedisCacheConfiguration.defaultCacheConfig().prefixKeysWith("( ͡° ᴥ ͡°)");

The following example shows how to set a computed prefix:

// computed key prefix
RedisCacheConfiguration.defaultCacheConfig().computePrefixWith(cacheName -> "¯\_(ツ)_/¯" + cacheName);

下表列出了 RedisCacheManager 的默认设置:

设置
缓存写入器非锁定
缓存配置RedisCacheConfiguration#defaultConfiguration
初始缓存
Trasaction Aware

下表列出了 RedisCacheConfiguration 的默认设置:

密钥有效期
缓存 null
前缀键
默认前缀实际缓存名称
Key SerializerStringRedisSerializer
Value SerializerJdkSerializationRedisSerializer
转换服务DefaultFormattingConversionService ,带有默认缓存密钥转换器

6. Reactive Redis支持

本节介绍了Redis的反应支持以及如何入门。 Reactive Redis支持自然与 imperative Redis support 有某些重叠。

6.1. Redis要求

Spring Data Redis目前与 Lettuce 集成为唯一的反应式Java连接器。 Project Reactor 用作反应组合库。

6.2. 使用反应式驱动程序连接到Redis

使用Redis和Spring时的首要任务之一是通过IoC容器连接到 Store 。为此,需要Java连接器(或绑定)。无论您选择哪个库,都必须使用 org.springframework.data.redis.connection 包及其 ReactiveRedisConnectionReactiveRedisConnectionFactory 接口来处理和检索活动 connections 到Redis。

6.2.1. Redis操作模式

Redis可以作为独立服务器运行,具有 Redis SentinelRedis Cluster 模式。 Lettuce 支持所有前面提到的连接类型。

6.2.2. ReactiveRedisConnection和ReactiveRedisConnectionFactory

ReactiveRedisConnection 是Redis通信的核心,因为它处理与Redis后端的通信。它还会自动将底层驱动程序异常转换为Spring的一致DAO异常 hierarchy ,因此您可以在不更改任何代码的情况下切换连接器,因为操作语义保持不变。

ReactiveRedisConnectionFactory 创建活动 ReactiveRedisConnection 实例。此外,工厂充当 PersistenceExceptionTranslator 实例,这意味着,一旦声明,它们就会让您进行透明异常转换 - 例如,通过使用 @Repository 注释和AOP进行异常转换。有关更多信息,请参阅Spring Framework文档中的专用 section

根据基础配置,工厂可以返回新连接或现有连接(如果使用池或共享本机连接)。

使用 ReactiveRedisConnectionFactory 的最简单方法是通过IoC容器配置相应的连接器并将其注入using类。

6.2.3. 配置 Lettuce 连接器

Spring Data Redis通过 org.springframework.data.redis.connection.lettuce 包支持 Lettuce

您可以按如下方式为Lettuce设置 ReactiveRedisConnectionFactory

@Bean
public ReactiveRedisConnectionFactory connectionFactory() {
  return new LettuceConnectionFactory("localhost", 6379);
}

以下示例显示了使用 LettuceClientConfigurationBuilder 的更复杂的配置,包括SSL和超时:

@Bean
public ReactiveRedisConnectionFactory lettuceConnectionFactory() {

  LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
    .useSsl().and()
    .commandTimeout(Duration.ofSeconds(2))
    .shutdownTimeout(Duration.ZERO)
    .build();

  return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379), clientConfig);
}

有关更详细的客户端配置调整,请参阅 LettuceClientConfiguration

6.3. 通过ReactiveRedisTemplate处理对象

大多数用户可能会使用 ReactiveRedisTemplate 及其相应的包 org.springframework.data.redis.core 。由于其丰富的功能集,模板实际上是Redis模块的中心类。该模板为Redis交互提供了高级抽象。虽然 ReactiveRedisConnection 提供了接受和返回二进制值( ByteBuffer )的低级方法,但该模板负责序列化和连接管理,使您无需处理此类详细信息。

此外,该模板提供操作视图(遵循Redis命令 reference 的分组),提供丰富的,通用的接口,用于处理特定类型,如下表所述:

接口说明
键类型操作
ReactiveGeoOperationsRedis地理空间操作,例如 GEOADDGEORADIUS 和其他)
ReactiveHashOperationsRedis 哈希操作
ReactiveHyperLogLogOperationsRedis HyperLogLog操作,例如( PFADDPFCOUNT 等)
ReactiveListOperationsRedis list 操作
ReactiveSetOperationsRedis set 操作
ReactiveValueOperationsRedis string(或value)操作
ReactiveZSetOperationsRedis zset(或有序集)操作

配置完成后,模板是线程安全的,可以跨多个实例重用。

ReactiveRedisTemplate 在其大多数操作中使用基于Java的序列化程序。这意味着模板编写或读取的任何对象都通过 RedisElementWriterRedisElementReader 序列化或反序列化。序列化上下文在构建时传递给模板,Redis模块提供 org.springframework.data.redis.serializer 包中的几个实现。有关更多信息,请参见 Serializers

以下示例显示 ReactiveRedisTemplate 用于返回 Mono

@Configuration
class RedisConfiguration {

  @Bean
  ReactiveRedisTemplate<String, String> reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
    return new ReactiveRedisTemplate<>(factory, RedisSerializationContext.string());
  }
}
public class Example {

  @Autowired
  private ReactiveRedisTemplate<String, String> template;

  public Mono<Long> addLink(String userId, URL url) {
    return template.opsForList().leftPush(userId, url.toExternalForm());
  }
}

6.4. 以字符串为中心的便利类

由于存储在Redis中的键和值通常是 java.lang.String ,因此Redis模块为 ReactiveRedisTemplateReactiveStringRedisTemplate 提供了基于字符串的扩展。对于密集的 String 操作,它是一种方便的一站式解决方案。除了绑定到 String 键之外,模板还使用基于字符串的 RedisSerializationContext ,这意味着存储的键和值是人类可读的(假设在Redis和代码中使用相同的编码)。以下示例显示正在使用的 ReactiveStringRedisTemplate

@Configuration
class RedisConfiguration {

  @Bean
  ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
    return new ReactiveStringRedisTemplate<>(factory);
  }
}
public class Example {

  @Autowired
  private ReactiveStringRedisTemplate redisTemplate;

  public Mono<Long> addLink(String userId, URL url) {
    return redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
  }
}

6.5. Redis Messaging / PubSub

Spring Data为Redis提供了专用的消息传递集成,在功能和命名方面与Spring Framework中的JMS集成非常相似;事实上,熟悉Spring中JMS支持的用户应该感到宾至如归。

Redis消息传递可以大致分为两个功能区域,即消息的生成或发布以及消费或订阅,因此快捷方式pubsub(发布/订阅)。 ReactiveRedisTemplate 类用于生成消息。对于异步接收,Spring Data提供了一个专用的消息监听器容器,用于使用消息流。出于订阅的目的 ReactiveRedisTemplate 提供了使用监听器容器的精简替代方案。

org.springframework.data.redis.connectionorg.springframework.data.redis.listener 提供了使用Redis消息传递的核心功能。

6.5.1. 发送/发布消息

要发布消息,可以像其他操作一样使用低级 ReactiveRedisConnection 或高级 ReactiveRedisTemplate 。两个实体都提供一种发布方法,该方法接受需要发送的消息以及目标通道作为参数。虽然 ReactiveRedisConnection 需要原始数据,但 ReactiveRedisTemplate 允许任意对象作为消息传入:

// send message through ReactiveRedisConnection
ByteBuffer msg = …
ByteBuffer channel = …
Mono<Long> publish = con.publish(msg, channel);

// send message through ReactiveRedisTemplate
ReactiveRedisTemplate template = …
Mono<Long> publish = template.convertAndSend("channel", "message");

6.5.2. 接收/订阅消息

在接收方,可以通过直接命名或使用模式匹配来订阅一个或多个通道。后一种方法非常有用,因为它不仅允许使用一个命令创建多个订阅,而且还可以监听尚未在订阅时创建的通道(只要它们与模式匹配)。

在低级别, ReactiveRedisConnection 提供了 subscribepSubscribe 方法,这些方法通过模式分别映射Redis命令以按通道订阅。请注意,可以使用多个通道或模式作为参数。要更改订阅,只需查询 ReactiveSubscription 的 Channels 和模式。

Spring Data Redis中的反向订阅命令是非阻塞的,可以在不发出元素的情况下终止。

如上所述,一旦订阅连接就开始等待消息。除了添加新订阅或修改/取消现有订阅外,不能在其上调用其他命令。 subscribepSubscribeunsubscribepUnsubscribe 以外的命令是非法的,会导致异常。

为了接收消息,需要获取消息流。请注意,订阅仅发布针对该特定订阅注册的 Channels 和模式的消息。消息流本身是一个热门序列,可以在不考虑需求的情况下生成元素。确保注册足够的需求以避免耗尽消息缓冲区。

消息侦听器容器

Spring Data提供 ReactiveRedisMessageListenerContainer ,它代表用户完成所有繁重的转换和订阅状态管理。

ReactiveRedisMessageListenerContainer 充当消息侦听器容器。它用于从Redis通道接收消息,并公开应用反序列化发出通道消息的消息流。它负责注册接收消息,资源获取和释放,异常转换等。这允许您作为应用程序开发人员编写与接收消息(并对其做出反应)相关联的(可能是复杂的)业务逻辑,并将样板Redis基础结构关注委托给框架。消息流在发布者订阅时在Redis中注册订阅,如果是,则取消注册订阅被取消。

此外,为了最小化应用程序占用空间, ReactiveRedisMessageListenerContainer 允许多个侦听器共享一个连接和一个线程,即使它们不共享订阅。因此,无论应用程序跟踪多少个侦听器或通道,运行时成本在其生命周期内将保持不变。此外,容器允许运行时配置更改,因此可以在应用程序运行时添加或删除侦听器,而无需重新启动。此外,容器使用延迟订阅方法,仅在需要时使用 ReactiveRedisConnection - 如果所有侦听器都已取消订阅,则会自动执行清理。

消息侦听器容器本身不需要外部线程资源。它使用驱动程序线程来发布消息。

ReactiveRedisConnectionFactory factory = …
ReactiveRedisMessageListenerContainer container = new ReactiveRedisMessageListenerContainer(factory);

Flux<ChannelMessage<String, String>> stream = container.receive(ChannelTopic.of("my-chanel"));
通过模板API订阅

如上所述,您可以直接使用 ReactiveRedisTemplate 订阅 Channels /模式。这种方法提供了一种直接的,虽然有限的解决方案,因为您放弃了在初始订阅后添加订阅的选项。尽管如此,您仍然可以通过返回的 Flux 使用例如控制消息流。 take(Duration) 。完成读取后,在出错或取消时,将再次释放所有绑定资源。

redisTemplate.listenToChannel("channel1", "channel2").doOnNext(msg -> {
    // message processing ...
}).subscribe();

6.6. Reactive Scripting

通过响应式基础架构执行Redis脚本可以通过 ReactiveRedisTemplate 使用 ReactiveScriptExecutor 访问最佳。

public class Example {

  @Autowired
  private ReactiveRedisTemplate<String, String> template;

  public Flux<Long> theAnswerToLife() {

    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
    script.setLocation(new ClassPathResource("META-INF/scripts/42.lua"));
    script.setResultType(Long.class);

    return reactiveTemplate.execute(script);
  }
}

有关脚本命令的更多详细信息,请参阅 scripting section

7. Redis群集

使用 Redis Cluster 需要Redis Server 3.0版。有关更多信息,请参阅 Cluster Tutorial

7.1. 启用Redis群集

群集支持基于与非群集通信相同的构建块。 RedisClusterConnectionRedisConnection 的扩展,处理与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()));
    }
}

RedisClusterConfiguration 也可以通过 PropertySource 定义,并具有以下属性:

配置属性

初始配置将驱动程序库指向一组初始集群节点。实时群集重新配置导致的更改仅保留在本机驱动程序中,不会写回配置。

7.2. 使用Redis群集连接

如前所述,Redis群集的行为与单节点Redis或甚至Sentinel监控的主副本环境不同。这是因为自动分片将密钥映射到16384个插槽之一,这些插槽分布在节点上。因此,涉及多个密钥的命令必须断言所有密钥映射到完全相同的插槽,以避免跨时隙执行错误。单个群集节点仅提供一组专用密钥。针对一个特定服务器发出的命令仅返回该服务器所服务的密钥的结果。举个简单的例子,考虑 KEYS 命令。当发布到群集环境中的服务器时,它仅返回请求发送到的节点所服务的密钥,而不一定返回群集中的所有密钥。因此,要获取群集环境中的所有密钥,必须从所有已知主节点读取密钥。

虽然驱动程序库会处理特定键到相应插槽服务节点的重定向,但更高级别的功能(例如跨节点收集信息或向集群中的所有节点发送命令)均由 RedisClusterConnection 涵盖。从前面提取密钥示例,这意味着 keys(pattern) 方法获取集群中的每个主节点,同时在每个主节点上执行 KEYS 命令,同时获取结果并返回累积的密钥集。要仅请求单个节点的密钥 RedisClusterConnection 为这些方法提供重载(例如, keys(node, pattern) )。

RedisClusterNode 可以从 RedisClusterConnection.clusterGetNodes 获得,也可以使用主机和端口或节点Id构建。

以下示例显示了在集群中运行的一组命令:

示例4.跨群集运行命令的示例

[email protected]: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]

当所有键映射到同一插槽时,本机驱动程序库会自动提供跨插槽请求,例如 MGET 。但是,如果不是这种情况, RedisClusterConnection 对插槽服务节点执行多个并行 GET 命令,并再次返回累积结果。这比单槽执行效率低,因此应谨慎使用。如果有疑问,请考虑通过在大括号中提供前缀(例如 {my-prefix}.thing1{my-prefix}.thing2 )将密钥固定到同一个插槽,这两个前缀都将映射到相同的插槽号。以下示例显示了跨槽请求处理:

示例5.交叉槽请求处理的示例

[email protected]: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键映射到同一个插槽→127.0.0.1:7381 MGET thing1 .thing2
3键映射到不同的插槽并分成单个插槽,路由到相应的节点→127.0.0.1:7379 GET thing2→127.0.0.1:7381 GET thing1

上述示例演示了Spring Data Redis遵循的一般策略。请注意,某些操作可能需要将大量数据加载到内存中以计算所需的命令。此外,并非所有交叉插槽请求都可以安全地移植到多个单插槽请求,如果误用,则会出错(例如, PFCOUNT )。

7.3. 使用RedisTemplate和ClusterOperations

有关 RedisTemplate 的一般用途,配置和用法的信息,请参见 Working with Objects through RedisTemplate 部分。

使用任何JSON RedisSerializers 设置 RedisTemplate#keySerializer 时要小心,因为更改JSON结构会立即影响哈希槽计算。

RedisTemplate 通过 ClusterOperations 接口提供对特定于集群的操作的访问,该接口可以从 RedisTemplate.opsForCluster() 获得。这使您可以在集群中的单个节点上显式运行命令,同时保留为模板配置的序列化和反序列化功能。它还提供管理命令(例如 CLUSTER MEET )或更多高级操作(例如,重新分片)。

以下示例显示如何使用 RedisTemplate 访问 RedisClusterConnection

示例6.使用 RedisTemplate 访问 RedisClusterConnection

ClusterOperations clusterOps = redisTemplate.opsForCluster();
clusterOps.shutdown(NODE_7379);                                              (1)
1在7379关闭节点并交叉手指有一个可以接管的复制品。

8. Redis存储库

使用Redis存储库可以在Redis Hashes中无缝转换和存储域对象,应用自定义映射策略以及使用二级索引。

Redis存储库至少需要Redis Server版本2.8.0,不适用于事务。确保使用 RedisTemplatedisabled transaction support

8.1. 用法

Spring Data Redis允许您轻松实现域实体,如以下示例所示:

示例7.示例人员实体

@RedisHash("people")
public class Person {

  @Id String id;
  String firstname;
  String lastname;
  Address address;
}

我们这里有一个非常简单的域对象。请注意,它的类型有一个 @RedisHash 注释,而 org.springframework.data.annotation.Id 注释了一个名为 id 的属性。这两个项目负责创建用于保持哈希的实际密钥。

使用 @Id 注释的属性以及名为 id 的属性被视为标识符属性。那些带有注释的人比其他人更受青睐。

现在实际上有一个负责存储和检索的组件,我们需要定义一个存储库接口,如下例所示:

示例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 或重新使用已设置的 id 值,并使用模式为 keyspace:id 的密钥在Redis哈希中存储 Person 类型的属性 - 在这种情况下,它可能是 people:5d67b7e1-8640-4475-beeb-c666fab4c0e5
2使用提供的 id 检索存储在 keyspace:id 的对象。
3计算可用实体的总数键空间 people ,由 @RedisHashPerson 上定义。
4从Redis中删除给定对象的键。

8.2. 对象映射基础知识

本节介绍Spring Data对象映射,对象创建,字段和属性访问,可变性和不变性的基础知识。注意,本节仅适用于不使用底层数据存储的对象映射的Spring Data模块(如JPA)。另外,请务必查阅特定于 Store 的部分以了解特定于 Store 的对象映射,例如索引,自定义列或字段名称等。

Spring Data对象映射的核心职责是创建域对象的实例,并将存储本机数据结构映射到这些对象上。这意味着我们需要两个基本步骤:

8.2.1. 对象创建

Spring Data会自动尝试检测持久化实体的构造函数,以用于实现该类型的对象。分辨率算法的工作原理如下:

值解析假定构造函数参数名称与实体的属性名称匹配,即将执行解析,就像要填充属性一样,包括映射中的所有自定义(不同的数据存储列或字段名称等)。这还需要类文件中可用的参数名称信息或构造函数上存在的 @ConstructorProperties 注释。

可以使用Spring Framework的 @Value 值注释使用特定于 Store 的SpEL表达式来自定义值解析。有关更多详细信息,请参阅有关 Store 特定映射的部分。

对象创建内部

为了避免反射的开销,Spring Data对象创建使用默认情况下在运行时生成的工厂类,它将直接调用域类构造函数。即对于此示例类型:

class Person {
  Person(String firstname, String lastname) { … }
}

我们将在运行时创建一个在语义上等效于此工厂类的工厂类:

class PersonObjectInstantiator implements ObjectInstantiator {

  Object newInstance(Object... args) {
    return new Person((String) args[0], (String) args[1]);
  }
}

这使得我们在反射方面的性能提升了10%。要使域类符合此类优化的条件,它需要遵守一组约束:

如果这些条件中的任何一个匹配,Spring Data将通过反射回退到实体实例化。

8.2.2. properties 人口

一旦创建了实体的实例,Spring Data就会填充该类的所有剩余持久属性。除非已经由实体的构造函数填充(即通过其构造函数参数列表使用),否则将首先填充identifier属性以允许循环对象引用的解析。之后,在实体实例上设置尚未由构造函数填充的所有非瞬态属性。为此,我们使用以下算法:

property 人口内部

与我们的 optimizations in object construction 类似,我们还使用Spring Data运行时生成的访问器类与实体实例进行交互。

class Person {

  private final Long id;
  private String firstname;
  private @AccessType(Type.PROPERTY) String lastname;

  Person() {
    this.id = null;
  }

  Person(Long id, String firstname, String lastname) {
    // Field assignments
  }

  Person withId(Long id) {
    return new Person(id, this.firstname, this.lastame);
  }

  void setLastname(String lastname) {
    this.lastname = lastname;
  }
}

示例11.生成的Property Accessor

class PersonPropertyAccessor implements PersistentPropertyAccessor {

  private static final MethodHandle firstname;              (2)

  private Person person;                                    (1)

  public void setProperty(PersistentProperty property, Object value) {

    String name = property.getName();

    if ("firstname".equals(name)) {
      firstname.invoke(person, (String) value);             (2)
    } else if ("id".equals(name)) {
      this.person = person.withId((Long) value);            (3)
    } else if ("lastname".equals(name)) {
      this.person.setLastname((String) value);              (4)
    }
  }
}
1PropertyAccessor持有底层对象的可变实例。这是为了实现其他不可变属性的突变。
2默认情况下,Spring Data使用字段访问来读取和写入属性值。根据 private 字段的可见性规则, MethodHandles 用于与字段交互。
3该类公开了一个用于设置标识符的 withId(…) 方法,例如将实例插入数据存储区并生成标识符时。调用 withId(…) 会创建一个新的 Person 对象。所有后续突变都将在新实例中发生,而前一个突变不会发生。
4使用property-access允许直接方法调用而不使用 MethodHandles

这使我们在反射方面的性能提升了25%。要使域类符合此类优化条件,它需要遵循一组限制:

默认情况下,如果检测到限制,Spring Data会尝试使用生成的属性访问器并回退到基于反射的访问器。

我们来看看以下实体:

示例12.样本实体

class Person {

  private final @Id Long id;                                                (1)
  private final String firstname, lastname;                                 (2)
  private final LocalDate birthday;
  private final int age; (3)

  private String comment;                                                   (4)
  private @AccessType(Type.PROPERTY) String remarks;                        (5)

  static Person of(String firstname, String lastname, LocalDate birthday) { (6)

    return new Person(null, firstname, lastname, birthday,
      Period.between(birthday, LocalDate.now()).getYears());
  }

  Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { (6)

    this.id = id;
    this.firstname = firstname;
    this.lastname = lastname;
    this.birthday = birthday;
    this.age = age;
  }

  Person withId(Long id) {                                                  (1)
    return new Person(id, this.firstname, this.lastname, this.birthday);
  }

  void setRemarks(String remarks) {                                         (5)
    this.remarks = remarks;
  }
}
1标识符属性为final,但在构造函数中设置为 null 。该类公开了一个用于设置标识符的 withId(…) 方法,例如将实例插入数据存储区并生成标识符时。原始 Person 实例在创建新实例时保持不变。相同的模式通常应用于存储管理的其他属性,但可能必须更改以进行持久性操作。
2firstnamelastname 属性是可能通过getter公开的普通不可变属性。
3age 属性是 birthday 属性中的不可变但派生的属性。在显示设计的情况下,数据库值将胜过默认值,因为Spring Data使用唯一声明的构造函数。即使意图是计算应该是首选,重要的是这个构造函数也将 age 作为参数(可能忽略它),否则属性填充步骤将尝试设置age字段并因为它是不可变的而失败。枯萎的存在。
4comment 属性是可变的,通过直接设置其字段来填充。
5remarks 属性是可变的并通过直接设置 comment 字段或通过调用的setter方法来填充
6该类公开了一个工厂方法和一个用于创建对象的构造函数。这里的核心思想是使用工厂方法而不是其他构造函数来避免构造函数通过 @PersistenceConstructor 消除歧义。相反,在工厂方法中处理属性的默认值。

8.2.3. 一般建议

8.2.4. Kotlin支持

Spring Data调整Kotlin的细节以允许对象创建和变异。

Kotlin对象创建

支持实例化Kotlin类,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下 data class Person

data class Person(val id: String, val name: String)

上面的类使用显式构造函数编译为典型的类。我们可以通过添加另一个构造函数来自定义此类,并使用 @PersistenceConstructor 注释它以指示构造函数首选项:

data class Person(var id: String, val name: String) {

    @PersistenceConstructor
    constructor(id: String) : this(id, "unknown")
}

如果未提供参数,则允许使用默认值,Kotlin支持参数选项。当Spring Data检测到具有参数默认值的构造函数时,如果数据存储没有,则它将保留这些参数提供一个值(或简单地返回 null ),以便Kotlin可以应用参数默认值。考虑以下为 name 应用参数默认值的类

data class Person(var id: String, val name: String = "unknown")

每次 name 参数不是结果的一部分或其值是 null 时, name 默认为 unknown

Kotlin数据类的属性数量

在Kotlin中,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下 data class Person

data class Person(val id: String, val name: String)

这个类实际上是不可变的。它允许创建新实例,因为Kotlin生成一个 copy(…) 方法,该方法创建新对象实例,从现有对象复制所有属性值,并将作为参数提供的属性值应用于方法。

8.3. 对象到哈希映射

Redis存储库支持将对象持久化为哈希。这需要一个由 RedisConverter 完成的Object-to-Hash转换。默认实现使用 Converter 将属性值映射到Redis本机 byte[]

给定前面部分中的 Person 类型,默认映射如下所示:

_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简单属性值按路径映射。
3复杂类型的属性由其点路径映射。

下表描述了默认的映射规则:

类型示例映射值
简单类型(例如,字符串)String firstname =“rand”;firstname =“rand”
复杂类型(例如,地址)地址地址=新地址(“emond的字段”);address.city =“emond's field”
简单类型列表List <String> nicknames = asList(“dragon reborn”,“lews therin”);昵称。[0] = "dragon reborn" ,昵称。[1] = "lews therin"
简单类型的 MapMap <String,String> atts = asMap({“eye-color”,“grey”},{“...atts。[eye-color] = "grey" ,atts。[hair-color] = “......
复杂类型列表列表<地址>地址= asList(新地址(“em ...地址。[0] .city = "emond’s field" ,地址。[1] .city =”...
复杂类型的 MapMap <String,Address> addresses = asMap({“home”,new Address(“em ...addresses。[home] .city = "emond’s field" ,addresses。[work] .city =”...

由于平面表示结构,Map键必须是简单类型,例如 StringNumber

通过在 RedisCustomConversions 中注册相应的 Converter ,可以自定义映射行为。这些转换器可以负责转换单个 byte[] 以及 Map<String,byte[]> 。第一个适用于(例如)将复杂类型转换为(例如)仍使用默认映射哈希结构的二进制JSON表示。第二个选项提供对结果哈希的完全控制。

将对象写入Redis哈希会从哈希中删除内容并重新创建整个哈希,因此未映射的数据将丢失。

以下示例显示了两个示例字节数组转换器:

示例13.示例byte []转换器

@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 转换器的两个示例:

示例14.示例Map <String,byte []> Converters

@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"

自定义转化对索引解析没有影响。即使是自定义转换类型,仍会创建 Secondary Indexes

8.3.1. 自定义类型映射

如果要避免将整个Java类名称写为类型信息并且更愿意使用键,则可以对要保留的实体类使用 @TypeAlias 注释。如果您需要更多地自定义映射,请查看 TypeInformationMapper 界面。可以在 DefaultRedisTypeMapper 配置该接口的实例,该实例可以在 MappingRedisConverter 上配置。

以下示例显示如何为实体定义类型别名:

示例15.为实体定义 @TypeAlias

@TypeAlias("pers")
class Person {

}

生成的文档包含 pers 作为 _class 字段中的值。

配置自定义类型映射

以下示例演示如何在 MappingRedisConverter 中配置自定义 RedisTypeMapper

示例16.通过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();
  }
}

8.4. Keyspaces

Keyspaces定义用于为Redis Hash创建实际密钥的前缀。默认情况下,前缀设置为 getClass().getName() 。您可以通过在聚合根级别设置 @RedisHash 来更改此默认值通过设置编程配置。但是,带注释的键空间将取代任何其他配置。

以下示例显示如何使用 @EnableRedisRepositories 注释设置键空间配置:

示例17.通过 @EnableRedisRepositories 设置Keyspace

@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"));
    }
  }
}

以下示例显示如何以编程方式设置键空间:

示例18.程序化Keyspace设置

@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"));
    }
  }
}

8.5. 二级索引

Secondary indexes 用于启用基于本机Redis结构的查找操作。值将在每次保存时写入相应的索引,并在删除对象或 expire 时删除。

8.5.1. 简单属性索引

给定前面显示的示例 Person 实体,我们可以通过使用 @Indexed 注释属性来为 firstname 创建索引,如以下示例所示:

示例19.注释驱动的索引

@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 注释。在这种情况下,一旦 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)
}
1SADD people:attributes.map-key:map-value e2c7dcee-b8cd-4424-883e-736ce564363e
2SADD people:relatives.map-key.firstname:tam e2c7dcee-b8cd-4424-883e-736ce564363e
3SADD people:addresses.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e

无法在 References 上解析索引。

与键空间一样,您可以配置索引而无需注释实际的域类型,如以下示例所示:

示例20.使用@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"));
    }
  }
}

同样,与键空间一样,您可以以编程方式配置索引,如以下示例所示:

示例21.程序化索引设置

@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"));
    }
  }
}

8.5.2. 地理空间索引

假设 Address 类型包含 Point 类型的 location 属性,该属性保存特定地址的地理坐标。通过使用 @GeoIndexed 对属性进行批注,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使用 PointDistance 对嵌套属性进行查询方法声明。
2在嵌套属性上查询方法声明,使用 Circle 进行搜索。
3GEOADD people:address:location 13.361389 38.115556 e2c7dcee-b8cd-4424-883e-736ce564363e
4GEORADIUS people:address:location 15.0 37.0 200.0 km

在前面的示例中,使用 GEOADD 存储经度和纬度值,该值使用对象的 id 作为成员的名称。 finder方法允许使用 CirclePoint, Distance 组合来查询这些值。

可以将 nearwithin 与其他条件结合使用 not

8.6. 按示例查询

8.6.1. 简介

本章介绍Query by Example并说明如何使用它。

按示例查询(QBE)是一种用户友好的查询技术,具有简单的界面。它允许动态创建查询,并且不需要您编写包含字段名称的查询。实际上,Query by Example不要求您使用特定于 Store 的查询语言来编写查询。

8.6.2. 用法

Query by Example API由三部分组成:

按示例查询非常适合几种用例:

按示例查询也有几个限制:

在开始使用Query by Example之前,您需要拥有一个域对象。首先,为存储库创建一个接口,如以下示例所示:

示例22.示例Person对象

public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

前面的示例显示了一个简单的域对象。您可以使用它来创建 Example 。默认情况下,将忽略具有 null 值的字段,并使用特定于 Store 的默认值匹配字符串。可以使用 of 工厂方法或使用 ExampleMatcher 构建示例。 Example 是不可变的。该以下列表显示了一个简单的示例

例23.简单的例子

Person person = new Person();                         (1)
person.setFirstname("Dave");                          (2)

Example<Person> example = Example.of(person);         (3)
1创建域对象的新实例。
2设置要查询的属性。
3创建 Example

理想情况下,可以使用存储库执行示例。为此,请让您的存储库接口扩展 QueryByExampleExecutor<T> 。以下清单显示了 QueryByExampleExecutor 界面的摘录:

例24. QueryByExampleExecutor

public interface QueryByExampleExecutor<T> {

  <S extends T> S findOne(Example<S> example);

  <S extends T> Iterable<S> findAll(Example<S> example);

  // … more functionality omitted.
}

8.6.3. 示例匹配器

示例不限于默认设置。您可以使用 ExampleMatcher 为字符串匹配,空值处理和特定于属性的设置指定自己的默认值,如以下示例所示:

示例25.具有定制匹配的示例匹配器

Person person = new Person();                          (1)
person.setFirstname("Dave");                           (2)

ExampleMatcher matcher = ExampleMatcher.matching()     (3)
  .withIgnorePaths("lastname")                         (4)
  .withIncludeNullValues()                             (5)
  .withStringMatcherEnding();                          (6)

Example<Person> example = Example.of(person, matcher); (7)
1创建域对象的新实例。
2设置属性。
3创建 ExampleMatcher 以期望所有值匹配。即使没有进一步配置,它也可以在这个阶段使用。
4构造一个新的 ExampleMatcher 以忽略 lastname 属性路径。
5构造一个新的 ExampleMatcher 以忽略 lastname 属性路径并包含空值。
6构造一个新的 ExampleMatcher 以忽略 lastname 属性路径,包含空值,并执行后缀字符串匹配。
7根据域对象和配置的 ExampleMatcher 创建新的 Example

默认情况下, ExampleMatcher 期望探针上设置的所有值都匹配。如果要获得与隐式定义的任何谓词匹配的结果,请使用 ExampleMatcher.matchingAny()

您可以指定单个属性的行为(例如“firstname”和“lastname”,或者对于嵌套属性,“address.city”)。您可以使用匹配选项和区分大小写来调整它,如以下示例所示:

示例26.配置匹配器选项

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}

配置匹配器选项的另一种方法是使用lambdas(在Java 8中引入)。此方法创建一个回调,要求实现者修改匹配器。您无需返回匹配器,因为配置选项保存在匹配器实例中。以下示例显示了使用lambdas的匹配器:

示例27.使用lambdas配置matcher选项

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

Example 创建的查询使用配置的合并视图。默认匹配设置可以在 ExampleMatcher 级别设置,而个别设置可以应用于特定属性路径。除非明确定义,否则 ExampleMatcher 上设置的设置将由属性路径设置继承。属性修补程序上的设置优先于默认设置。下表描述了各种 ExampleMatcher 设置的范围:

设置范围
无效处理ExampleMatcher
字符串匹配ExampleMatcher 和属性路径
忽略属性属性路径
区分大小写ExampleMatcher 和属性路径
值转换属性路径

8.6.4. 执行示例

以下示例针对存储库使用Query by Example:

示例28.使用存储库按示例查询

interface PersonRepository extends QueryByExampleExecutor<Person> {
}

class PersonService {

  @Autowired PersonRepository personRepository;

  List<Person> findPeople(Person probe) {
    return personRepository.findAll(Example.of(probe));
  }
}

Redis Repositories通过其二级索引支持Spring Data的Query by Example功能的子集。特别是,只使用精确,区分大小写和非空值来构造查询。

辅助索引使用基于集合的操作(Set intersection,Set union)来确定匹配的键。将属性添加到未编制索引的查询不会返回任何结果,因为不存在索引。 “按示例查询”支持检查索引配置,以仅包括索引所涵盖的查询中的属性。这是为了防止意外包含非索引属性。

不区分大小写的查询和不受支持的 StringMatcher 实例在运行时被拒绝。

以下列表显示了受支持的Query by Example选项:

以下列表显示Query by Example不支持的属性:

8.7. 生存时间

存储在Redis中的对象可能仅在一定时间内有效。这对于在Redis中持久存在短期对象尤其有用,而无需手动删除它们当他们达到他们的生命结束。以秒为单位的到期时间可以使用 @RedisHash(timeToLive=…) 设置,也可以使用 KeyspaceSettings 设置(请参阅 Keyspaces )。

通过在数字属性或方法上使用 @TimeToLive 注释,可以设置更灵活的到期时间。但是,不要对同一类中的方法和属性应用 @TimeToLive 。以下示例显示属性和方法上的 @TimeToLive 注释:

例29.过期

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();
  }
}

使用 @TimeToLive 显式注释属性会从Redis读回实际的 TTLPTTL 值。 -1表示该对象没有关联的到期日期。

存储库实现确保通过 RedisMessageListenerContainer 订阅 Redis keyspace notifications

当到期时间设置为正值时,将执行相应的 EXPIRE 命令。除了保留原始版本之外,还有一个幻像副本在Redis中保留,并在原始版本后五分钟到期。这样做是为了使存储库支持能够发布 RedisKeyExpiredEvent ,并在密钥到期时保持Spring的 ApplicationEventPublisher 中的过期值,即使原始值已被删除。使用Spring Data Redis存储库的所有连接应用程序都会收到到期事件。

默认情况下,在初始化应用程序时禁用密钥到期侦听器。可以在 @EnableRedisRepositoriesRedisKeyValueAdapter 中调整启动模式以使用应用程序启动侦听器,或者在第一次插入具有TTL的实体时启动侦听器。有关可能的值,请参见 EnableKeyspaceEvents

RedisKeyExpiredEvent 包含过期域对象的副本以及密钥。

延迟或禁用到期事件侦听器启动会影响 RedisKeyExpiredEvent publishing。已禁用的事件侦听器不会发布到期事件。由于延迟的侦听器初始化,延迟启动可能导致事件丢失。

键空间通知消息侦听器在Redis中更改 notify-keyspace-events 设置(如果尚未设置)。不会覆盖现有设置,因此您必须正确设置这些设置(或将其保留为空)。请注意,在AWS ElastiCache上禁用了 CONFIG ,并且启用侦听器会导致错误。

Redis发布/订阅消息不是永久性的。如果密钥在应用程序关闭时到期,则不会处理到期事件,这可能导致包含对已过期对象的引用的二级索引。

8.8. 持久参考文献

使用 @Reference 标记属性允许存储简单的键引用,而不是将值复制到哈希本身。从Redis加载时,引用会自动解析并映射回对象,如以下示例所示:

示例30.示例属性参考

_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 )。

保存引用对象时,参考对象不会保留。您必须单独保留对引用对象的更改,因为只存储引用。未解析在引用类型的属性上设置的索引。

8.9. 持久的部分更新

在某些情况下,您无需加载和重写整个实体,只需在其中设置新值即可。上次活动时间的会话时间戳可能是您想要更改一个属性的场景。 PartialUpdate 允许您在现有对象上定义 setdelete 操作,同时负责更新实体本身和索引结构的潜在过期时间。以下示例显示了部分更新:

示例31.部分更新示例

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设置值映射,删除以前存在的映射并用给定值替换值。
6更改 Time To Live 时自动更新服务器到期时间。

更新复杂对象以及 Map (或其他集合)结构需要与Redis进一步交互以确定现有值,这意味着重写整个实体可能会更快。

8.10. 查询和查询方法

查询方法允许从方法名称自动派生简单的查找程序查询,如以下示例所示:

示例32.示例存储库查找器方法

public interface PersonRepository extends CrudRepository<Person, String> {

  List<Person> findByFirstname(String firstname);
}

请确保在finder方法中使用的属性已设置为索引。

Redis存储库的查询方法仅支持对具有分页的实体和实体集合的查询。使用派生查询方法可能并不总是足以模拟要执行的查询。 RedisCallback 提供了对索引结构甚至自定义索引的实际匹配的更多控制。为此,请提供 RedisCallback ,它返回单个或 Iterableid 值集,如以下示例所示:

示例33.使用RedisCallback的样本查找器

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支持的关键字以及包含该关键字的方法实质上转换为:

关键字示例Redis代码段
AndfindByLastnameAndFirstnameSINTER …:firstname:rand …:lastname:al’thor
OrfindByLastnameOrFirstnameSUNION …:firstname:rand …:lastname:al’thor
Is, EqualsfindByFirstnamefindByFirstnameIsfindByFirstnameEqualsSINTER …:firstname:rand
IsTrueFindByAliveIsTrueSINTER …:alive:1
IsFalsefindByAliveIsFalseSINTER …:alive:0
Top,FirstfindFirst10ByFirstnamefindTop5ByFirstname

8.11. Redis存储库在群集上运行

您可以在群集Redis环境中使用Redis存储库支持。有关 ConnectionFactory 配置详细信息,请参阅“ Redis Cluster ”部分。但是,必须进行一些其他配置,因为默认密钥分发会将实体和二级索引分散到整个群集及其插槽中。

下表显示了群集上数据的详细信息(基于前面的示例):

类型插槽节点
人:e2c7dcee-b8cd-4424-883e-736ce564363eid for hash15171127.0.0.1:7381
人:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56id for hash7373127.0.0.1:7380
人:名字:randindex1700127.0.0.1:7379

某些命令(例如 SINTERSUNION )只能在所有相关键映射到同一插槽时在服务器端进行处理。否则,必须在客户端进行计算。因此,将键空间固定到单个插槽是很有用的,这样可以立即使用Redis服务器端计算。下表显示了执行操作时发生的情况(请注意插槽列中的更改以及节点列中的端口值):

类型插槽节点
:e2c7dcee-b8cd-4424-883e-736ce564363eid for hash2399127.0.0.1:7379
:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56id for hash2399127.0.0.1:7379
:名字:randindex2399127.0.0.1:7379

使用Redis群集时,使用 @RedisHash("{yourkeyspace}") 定义和固定密钥空间到特定的插槽。

8.12. CDI整合

存储库接口的实例通常由容器创建,在使用Spring Data时,Spring是最自然的选择。 Spring为创建bean实例提供了复杂功能。 Spring Data Redis附带了一个自定义CDI扩展,允许您在CDI环境中使用存储库抽象。扩展是JAR的一部分,因此,要激活它,请将Spring Data Redis JAR拖放到类路径中。

然后,您可以通过为 RedisConnectionFactoryRedisOperations 实现CDI Producer来设置基础结构,如以下示例所示:

class RedisOperationsProducer {


  @Produces
  RedisConnectionFactory redisConnectionFactory() {

    JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(new RedisStandaloneConfiguration());
    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环境。

Spring Data Redis CDI扩展将所有可用的存储库作为CDI bean选取,并在容器请求存储库类型的bean时为Spring Data存储库创建代理。因此,获取Spring Data存储库的实例是声明 @Injected 属性的问题,如以下示例所示:

class RepositoryClient {

  @Inject
  PersonRepository repository;

  public void businessMethod() {
    List<Person> people = repository.findAll();
  }
}

Redis存储库需要 RedisKeyValueAdapterRedisKeyValueTemplate 个实例。如果未找到提供的bean,则由Spring Data CDI扩展创建和管理这些bean。但是,您可以提供自己的bean来配置 RedisKeyValueAdapterRedisKeyValueTemplate 的特定属性。

8.13. Redis存储库解剖

Redis作为 Store 本身提供了一个非常狭窄的低级API,从而将更高级别的功能(例如二级索引和查询操作)留给用户。

本节提供了存储库抽象发出的命令的更详细视图,以便更好地理解潜在的性能影响。

将以下实体类视为所有操作的起点:

例34.示例实体

@RedisHash("people")
public class Person {

  @Id String id;
  @Indexed String firstname;
  String lastname;
  Address hometown;
}

public class Address {

  @GeoIndexed Point location;
}

8.13.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>中写入的哈希的键添加到具有属性值的firstnames的二级索引中。
4将索引<3>添加到条目的辅助结构集中,以跟踪要在删除/更新时清除的索引。

8.13.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检查索引是否存在以及它是什么类型(text,geo,...)。
6从索引中删除可能存在的密钥。
7删除包含索引信息的帮助程序。
8将<2>中添加的哈希的键添加到具有属性值的firstnames的二级索引中。
9将索引<6>添加到条目的辅助结构集中,以跟踪要在删除/更新时清除的索引。

8.13.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将已保存条目的密钥添加到地理索引中。
2跟踪索引结构。

8.13.4. 使用简单索引查找

repository.findByFirstname("egwene");
SINTER  "people:firstname:egwene"                     (1)
HGETALL "people:d70091b5-0b9a-4c0a-9551-519e61bc9ef3" (2)
HGETALL ...
1获取辅助索引中包含的密钥。
2分别获取<1>返回的每个键。

8.13.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>返回的每个键。

附录文件结构

附录包含各种其他细节,以补充参考文档其余部分中的信息:

附录A:架构

Spring Data Redis Schema (redis-namespace)

附录B:命令参考

支持的命令

命令模板支持
APPENDX
AUTHX
BGREWRITEAOFX
BGSAVEX
BITCOUNTX
BITFIELDX
BITOPX
BLPOPX
BRPOPX
BRPOPLPUSHX
客户杀死X
客户端GETNAMEX
客户列表X
客户端SETNAMEX
CLUSTER SLOTS-
COMMAND-
COMMAND COUNT-
COMMAND GETKEYS-
COMMAND INFO-
CONFIG GETX
CONFIG RESETSTATX
CONFIG REWRITE-
CONFIG SETX
DBSIZEX
DEBUG OBJECT-
DEBUG SEGFAULT-
DECRX
DECRBYX
DELX
DISCARDX
DUMPX
ECHOX
EVALX
EVALSHAX
EXECX
EXISTSX
EXPIREX
EXPIREATX
FLUSHALLX
FLUSHDBX
GETX
GETBITX
GETRANGEX
GETSETX
HDELX
HEXISTSX
HGETX
HGETALLX
HINCRBYX
HINCRBYFLOATX
HKEYSX
HLENX
HMGETX
HMSETX
HSCANX
HSETX
HSETNXX
HVALSX
INCRX
INCRBYX
INCRBYFLOATX
INFOX
KEYSX
LASTSAVEX
LINDEXX
LINSERTX
LLENX
LPOPX
LPUSHX
LPUSHXX
LRANGEX
LREMX
LSETX
LTRIMX
MGETX
MIGRATE-
MONITOR-
MOVEX
MSETX
MSETNXX
MULTIX
OBJECT-
PERSISTX
PEXIPREX
PEXPIREATX
PFADDX
PFCOUNTX
PFMERGEX
PINGX
PSETEXX
PSUBSCRIBEX
PTTLX
PUBLISHX
PUBSUB-
PUBSUBSCRIBE-
QUITX
RANDOMKEYX
重新命名X
RENAMENXX
RESTOREX
角色-
RPOPX
RPOPLPUSHX
RPUSHX
RPUSHXX
SADDX
SAVEX
SCANX
SCARDX
SCRIPT EXITSX
SCRIPT FLUSHX
SCRIPT KILLX
SCRIPT LOADX
SDIFFX
SDIFFSTOREX
SELECTX
SENTINEL FAILOVERX
SENTINEL GET-MASTER-ADD-BY-NAME-
SENTINEL MASTER-
SENTINEL MASTERSX
SENTINEL MONITORX
SENTINEL REMOVEX
SENTINEL RESET-
SENTINEL SET-
SENTINEL SLAVESX
SETX
SETBITX
SETEXX
SETNXX
SETRANGEX
SHUTDOWNX
SINTERX
SINTERSTOREX
SISMEMBERX
SLAVEOFX
SLOWLOG-
SMEMBERSX
SMOVEX
SORTX
SPOPX
SRANDMEMBERX
SREMX
SSCANX
STRLENX
SUBSCRIBEX
SUNIONX
SUNIONSTOREX
SYNC-
TIMEX
TTLX
TYPEX
UNSUBSCRIBEX
UNWATCHX
WATCHX
ZADDX
ZCARDX
ZCOUNTX
ZINCRBYX
ZINTERSTOREX
ZLEXCOUNT-
ZRANGEX
ZRANGEBYLEX-
ZREVRANGEBYLEX-
ZRANGEBYSCOREX
ZRANKX
ZREMX
ZREMRANGEBYLEX-
ZREMRANGEBYRANKX
ZREVRANGEX
ZREVRANGEBYSCOREX
ZREVRANKX
ZSCANX
ZSCOREX
ZUNINONSTOREX