5. Redis 支持
Spring Data支持的一个键值存储Redis。 引用项目主页的话:
Redis是一个先进的键值存储。 它与memcached类似,但数据集不易变,值可以是字符串,就像在memcached中一样,也可以是列表,集合和有序集合。 所有这些数据类型都可以使用原子操作来操作push / pop元素,添加/删除元素,执行服务器端联合,交集,集合之间的差异等等。 Redis支持不同类型的排序功能。
Spring Data Redis提供了从Spring应用程序轻松配置和访问Redis的功能。 它提供了与存储互动的低级别和高级别抽象,使用户免受基础设施问题的困扰。
5.1. Redis要求
Spring Redis需要Redis 2.6或更高版本以及Java SE 8.0或更高版本。 在语言绑定(或连接器)方面,Spring Redis集成了两个受欢迎的Redis开源Java库Jedis和Lettuce。
5.2. Redis支持高级视图
Redis支持提供了几个组件(按依赖顺序):
对于大多数任务来说,高层次的抽象和支持服务是最好的选择。 请注意,在任何时候,可以在层之间移动 - 例如,容易保持低级别连接(甚至是本地库)与Redis直接通信。
5.3. 连接到Redis
使用Redis和Spring的首要任务之一是通过IoC容器连接到存储。 为此,需要Java连接器(或绑定)。 无论选择哪个库,只有一组Spring Data Redis API需要使用,它们在所有连接器(即org.springframework.data.redis.connection包及其RedisConnection和RedisConnectionFactory接口)之间运行一致 并检索到Redis的活动连接。
5.3.1. RedisConnection和RedisConnectionFactory
RedisConnection为Redis通信提供构建块,因为它处理与Redis后端的通信。 它还自动将底层连接库异常转换为Spring一致的DAO异常层次结构,以便在不改变代码的情况下切换连接器,因为操作语义保持不变。
对于需要本地库API的情况,RedisConnection提供了一个专用方法getNativeConnection,它返回用于通信的原始基础对象。
活动的RedisConnection是通过RedisConnectionFactory创建的。 另外,工厂充当PersistenceExceptionTranslator,意味着一旦声明,他们允许做透明异常转换。 例如,通过使用@Repository注释和AOP进行异常转换。 有关更多信息,请参阅Spring Framework文档中的专用章节。
根据底层配置,工厂可以返回一个新的连接或一个现有的连接(如果使用池或共享本地连接)。
使用RedisConnectionFactory的最简单方法是通过IoC容器配置适当的连接器,并将其注入到使用的类中。
不幸的是,目前并非所有的连接器都支持所有的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. 配置Lettuce连接器
Lettuce是Spring Data Redis通过org.springframework.data.redis.connection.lettuce包支持的基于netty的开源连接器。
它的配置可能很容易猜到:
<?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 Sentinel 支持
为了处理高可用性的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来定义。
配置属性
spring.redis.sentinel.master:主节点的名称。 spring.redis.sentinel.nodes`:主机:端口对的逗号分隔列表。
有时需要直接与Sentinels交互。 使用RedisConnectionFactory.getSentinelConnection()或RedisConnection.getSentinelCommands()可以访问配置的第一个活动Sentinel。
5.5. 通过RedisTemplate处理对象
大多数用户可能使用RedisTemplate及其相应的包org.springframework.data.redis.core - 由于其丰富的功能集,该模板实际上是Redis模块的中心类。 该模板提供了Redis交互的高级抽象。 虽然RedisConnection提供接受和返回二进制值(字节数组)的底层方法,但是模板负责序列化和连接管理,使用户不必处理这些细节。
此外,该模板还提供了操作视图(来自Redis命令参考的分组),该视图提供丰富的,通用的界面,用于处理特定类型或特定键(通过KeyBound界面),如下所述:
表1.操作视图
Interface | Description |
---|---|
GeoOperations | Redis geospatial operations likeGEOADD ,GEORADIUS ,…) |
HashOperations | Redis hash operations |
HyperLogLogOperations | Redis HyperLogLog operations like (PFADD ,PFCOUNT ,…) |
ListOperations | Redis list operations |
SetOperations | Redis set operations |
ValueOperations | Redis string (or value) operations |
ZSetOperations | Redis zset (or sorted set) operations |
Key Bound Operations | |
BoundGeoOperations | Redis key bound geospatial operations. |
BoundHashOperations | Redis hash key bound operations |
BoundKeyOperations | Redis key bound operations |
BoundListOperations | Redis list key bound operations |
BoundSetOperations | Redis set key bound operations |
BoundValueOperations | Redis string (or value) key bound operations |
BoundZSetOperations | Redis zset (or sorted set) key bound operations |
配置完成后,该模板是线程安全的,可以在多个实例中重复使用。
开箱即用,RedisTemplate在其大部分操作中使用基于Java的序列化程序。 这意味着模板写入或读取的任何对象都将通过Java进行序列化/反序列化。 序列化机制可以很容易地在模板上进行更改,而Redis模块在org.springframework.data.redis.serializer包中提供了几个可用的实现 - 请参阅序列化程序以获取更多信息。 您还可以将任何序列化程序设置为null,并通过将enableDefaultSerializer属性设置为false来对原始字节数组使用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提供了两个扩展,作为便捷的一站式解决方案 密集的字符串操作 除了绑定到字符串键,模板和连接使用下面的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的实例。
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本身支持各种类型,但大部分是指数据存储的方式,而不是它所代表的内容。 由用户决定是否将信息转换为字符串或其他对象。
用户(自定义)类型和原始数据(反之亦然)之间的转换在org.springframework.data.redis.serializer包中的Spring Data Redis中处理。
这个包包含两种类型的序列化程序,顾名思义,它们负责序列化过程:
- 基于`RedisSerializer`的双向序列化器。
- 元素读和写使用RedisElementReader和RedisElementWriter。
这些变体之间的主要区别在于,RedisSerializer主要序列化为byte [],而读和作使用ByteBuffer。
可以直接使用多种实现,其中的两个已经在本文档中提到过了:
StringRedisSerializer
JdkSerializationRedisSerializer
但是可以通过Spring OXM支持使用OxmSerializer进行Object / XML映射,或者使用Jackson2JsonRedisSerializer或GenericJackson2JsonRedisSerializer以JSON格式存储数据。
请注意,存储格式不仅限于值 - 它可以用于Key,值或散列值没有任何限制。
5.8. Hash mapping(散列映射)
您已经可以了解到可以转换JSON格式的对象的Jackson2JsonRedisSerializer。 JSON可以理想地存储为使用普通键的值。 使用Redis哈希可以实现更复杂的结构化对象映射。 Spring Data Redis提供了根据用例将数据映射到散列的各种策略。
- 使用HashOperations和一个序列化器直接映射
- 使用 Redis Repositories
- 使用HashMapper和HashOperations
5.8.1.Hash mappers(哈希映射器)
哈希映射器是将对象映射到Map <K,V>和返回的转换器。 HashMapper旨在与Redis哈希一起使用。
开箱即可使用多种实现方式:
- BeanUtilsHashMapper使用Spring的BeanUtils。
- 使用Object到Hash映射的ObjectHashMapper。
- Jackson2HashMapper使用FasterXML Jackson。
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哈希映射。 Jackson2HashMapper可以将数据映射顶级属性映射为哈希字段名称,并可选地将结构展平。 简单的类型映射到简单的值。 复杂类型(嵌套对象,集合,映射)作为嵌套的JSON。
拼合为所有嵌套属性创建单独的哈希条目,并尽可能将复杂类型解析为简单类型。
public class Person {
String firstname;
String lastname;
Address address;
}
public class Address {
String city;
String country;
}
Table 2. Normal Mapping
Hash Field | Value |
---|---|
firstname | Jon |
lastname | Snow |
address | { "city" : "Castle Black", "country" : "The North" } |
Table 3. Flat Mapping
Hash Field | Value |
---|---|
firstname | Jon |
lastname | Snow |
address.city | Castle Black |
address.country | The North |
展平需要所有属性名称不会干扰JSON路径。 在拼图键中使用点或括号或作为属性名称使用拼合不支持。 生成的散列无法映射回对象。
5.9. Redis Messaging/PubSub
Spring Data为Redis提供了专门的消息集成,在功能和命名方面非常类似于Spring Framework中的JMS集成; 实际上,熟悉Spring的JMS支持的用户应该感到宾至如归。
Redis消息可以大致分为两个功能区域,即生产或发布和消费或订阅消息,因此是快捷方式pubsub(发布/订阅)。 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。 两个实体都提供发布方法,该方法接受需要发送的消息以及目标频道作为参数。 虽然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提供了subscribe和pSubscribe方法,分别按照模式映射Redis命令用于按频道订阅。 请注意,多个通道或模式可以用作参数。 要更改连接的订阅或仅查询是否正在侦听,RedisConnection提供了getSubscription和isSubscribed方法。
Spring Data Redis中的订阅命令是阻塞的。 也就是说,在一个连接上调用订阅将导致当前线程阻塞,因为它将开始等待消息 - 只有当订阅被取消时,线程才会被释放,这是一个额外的线程在相同的连接上调用取消订阅或取消订阅。 有关此问题的解决方案,请参阅下面的消息监听器容器。
如上所述,一旦订阅连接开始等待消息。 除了添加新订阅或修改/取消现有订阅之外,不能调用其他命令。 也就是说,调用任何其他方法,然后订阅,pSubscribe,unsubscribe或pUnsubscribe都是非法的,并且会抛出异常。
为了订阅消息,需要实现MessageListener回调:每当新消息到达时,回调被调用,用户代码通过onMessage方法执行。 该界面不仅可以访问实际的消息,而且还可以访问已经接收到的通道以及订阅所使用的模式(如果有)以匹配通道。 该信息允许被叫方区分各种消息,不仅仅是内容,而且也是通过数据。
消息监听器容器
由于其阻塞性质,低级订阅没有吸引力,因为它需要为每个单独的监听者进行连接和线程管理。 为了缓解这个问题,Spring Data提供了RedisMessageListenerContainer,它代表用户完成所有繁重的工作 - 熟悉EJB和JMS的用户应该找到熟悉的概念,因为它尽可能地接近Spring框架中的支持, POJO(MDPs)
RedisMessageListenerContainer充当消息侦听器容器; 它用于接收来自Redis通道的消息,并驱动注入到其中的MessageListener。 侦听器容器负责所有的消息接收线程,并派发到侦听器进行处理。 消息监听器容器是MDP和消息提供者之间的中介,负责注册接收消息,资源获取和释放,异常转换等。 这使您可以作为应用程序开发人员编写与接收消息(并对其作出响应)相关的(可能是复杂的)业务逻辑,并将样板化的Redis基础架构问题委托给框架。
此外,为了最大限度地减少应用程序的占用空间,RedisMessageListenerContainer允许一个连接和一个线程被多个监听器共享,即使它们不共享订阅。 因此,无论应用程序跟踪多少个监听器或通道,运行时成本在其整个生命周期中都将保持不变。 此外,容器允许运行时配置更改,以便在应用程序运行时添加或删除侦听器,而无需重新启动。 此外,容器使用延迟订阅方式,仅在需要时才使用RedisConnection - 如果所有侦听器都已取消订阅,则会自动执行清除操作,并释放所用的线程。
为了帮助消息的异步方式,容器需要一个java.util.concurrent.Executor(或者Spring的TaskExecutor)来调度消息。 根据负载,监听器数量或运行时环境,应该更改或调整执行程序以更好地满足其需求 - 特别是在托管环境(如应用程序服务器)中,强烈建议选择合适的TaskExecutor 它的运行时间的优势。
MessageListenerAdapte
MessageListenerAdapter类是Spring异步消息传递支持中的最后一个组件:简而言之,它允许您将几乎任何类作为MDP公开(当然还有一些限制)。
考虑下面的接口定义。 请注意,虽然接口不扩展MessageListener接口,但仍然可以通过使用MessageListenerAdapter类将其用作MDP。 还要注意各种消息处理方法是如何根据它们可以接收和处理的各种消息类型的内容进行强类型化的。 另外,消息发送到的通道或模式可以作为String类型的第二个参数传递给方法:
public interface MessageDelegate {
void handleMessage(String message);
void handleMessage(Map message); void handleMessage(byte[] message);
void handleMessage(Serializable message);
// pass the channel/pattern as well
void handleMessage(Serializable message, String channel);
}
public class DefaultMessageDelegate implements MessageDelegate {
// implementation elided for clarity...
}
尤其要注意MessageDelegate接口(上面的DefaultMessageDelegate类)的上述实现如何根本没有Redis依赖关系。 这是一个POJO,我们将通过以下配置将其制作成MDP。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:redis="http://www.springframework.org/schema/redis"
xsi:schemaLocation="http://www.springframework.org/schema/beans 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 可以是一个 channel (例如. topic="chatroom") or a pattern (例如 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 事务
Redis通过multi,exec和discard命令提供对事务的支持。 这些操作在RedisTemplate上可用,但RedisTemplate不能保证使用相同的连接执行事务中的所有操作。
Spring Data Redis提供SessionCallback接口,以便在需要使用相同连接执行多个操作时使用,就像使用Redis事务一样。 例如:
//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 ops in the transaction
return operations.exec();
}
});
System.out.println("Number of items added to set: " + txResults.get(0));
在返回之前,RedisTemplate将使用其值,散列键和散列值序列化器来反序列化exec的所有结果。 还有一个额外的exec方法,允许您传递一个自定义序列化程序的事务结果。
版本1.1中对RedisConnection和RedisTemplate的exec方法做了一个重要的改变。 以前这些方法直接从连接器返回事务结果。 这意味着数据类型往往不同于RedisConnection方法返回的数据类型。 例如,zAdd返回一个布尔值,指示该元素已被添加到已排序的集合中。 大多数连接器将这个值作为一个long值返回,并且Spring Data Redis执行转换。 另一个常见的区别是,大多数连接器返回状态回复(通常是字符串“确定”)像集操作。 这些回复通常被Spring Data Redis丢弃。 在1.1之前,这些转换不是在exec的结果上执行的。 另外,结果在RedisTemplate中没有反序列化,所以它们通常包含原始字节数组。 如果此更改中断了您的应用程序,则可以在RedisConnectionFactory上将convertPipelineAndTxResults设置为false以禁用此行为。
5.10.1. @Transactional 注解的支持
事务支持默认是禁用的,必须通过设置setEnableTransactionSupport(true)为每个正在使用的RedisTemplate显式启用。 这将强制绑定正在使用的RedisConnection到当前线程触发MULTI。 如果事务完成没有错误,则调用EXEC,否则DISCARD。 一旦进入MULTI,RedisConnection会对写入操作进行排队,所有只读操作(例如KEYS)都被传送到新的(非线程绑定的)RedisConnection。
/** Sample Configuration **/
@Configuration
public class RedisTxContextConfiguration {
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// explicitly enable transaction support
template.setEnableTransactionSupport(true);
return template;
}
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public RedisConnectionFactory redisConnectionFactory( // jedis || lettuce);
@Bean
public DataSource dataSource() throws SQLException { // ... }
}
/** Usage Constrainsts **/
// executed on thread bound connection
template.opsForValue().set("foo", "bar");
// read operation executed on a free (not tx-aware)
connection template.keys("*");
// returns null as values set within transaction are not visible
template.opsForValue().get("foo");
5.11. Pipelining(管道)
Redis提供对管道的支持,其中包括向服务器发送多个命令,而不必等待答复,然后单步读取答复。 当您需要连续发送多个命令时,管道传输可以提高性能,例如将多个元素添加到同一个列表中。
Spring Data Redis提供了多个用于在管道中执行命令的RedisTemplate方法。 如果您不关心管道操作的结果,则可以使用标准的execute方法,对pipeline参数传递true。 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;
}
});
上面的例子执行流水线队列中项目的批量权限弹出。 结果列表包含所有弹出的项目。 RedisTemplate在返回之前使用它的值,散列键和散列值序列化器来反序列化所有的结果,所以上面例子中返回的项目就是Strings。 还有其他的executePipelined方法可以让你传递一个自定义的序列化器来进行流水线结果。
请注意,从RedisCallback返回的值必须为空,因为此值将被丢弃,以便返回流水线命令的结果。
版本1.1中对RedisConnection的closePipeline方法做了一个重要的改变。 以前,此方法直接从连接器返回流水线操作的结果。 这意味着数据类型通常不同于RedisConnection方法返回的数据类型。 例如,zAdd返回一个布尔值,指示该元素已被添加到已排序的集合中。 大多数连接器将这个值作为一个long值返回,并且Spring Data Redis执行转换。 另一个常见的区别是,大多数连接器返回状态回复(通常是字符串“确定”)像集操作。 这些回复通常被Spring Data Redis丢弃。 在1.1之前,不会对closePipeline的结果执行这些转换。 如果此更改中断了您的应用程序,则可以在RedisConnectionFactory上将convertPipelineAndTxResults设置为false以禁用此行为。
5.12. Redis 脚本
Redis 2.6及更高版本通过eval和evalsha命令为Lua脚本的执行提供支持。 Spring Data Redis为处理序列化的脚本执行提供高级抽象,并自动使用Redis脚本缓存。
脚本可以通过RedisTemplate和ReactiveRedisTemplate的执行方法运行。 两者都使用可配置的ScriptExecutor / ReactiveScriptExecutor运行提供的脚本。 默认情况下,ScriptExecutor负责序列化提供的键和参数,并反序列化脚本结果。 这是通过模板的键和值序列化器完成的。 还有一个额外的重载允许你传递脚本参数和结果的自定义序列化器。
默认的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应该是Long,Boolean,List或反序列化的值类型之一。 如果脚本返回丢弃状态(即“OK”),则也可以为null。 在应用程序上下文中配置DefaultRedisScript的单个实例是非常理想的,以避免在每个脚本执行过程中重新计算脚本的SHA1。
上面的checkAndSet方法然后执行脚本可以作为事务或管道的一部分在SessionCallback中执行。 有关更多信息,请参阅Redis事务和管道传输。
Spring Data Redis提供的脚本支持还允许您使用Spring Task和Scheduler抽象计划定期执行的Redis脚本。 有关更多详细信息,请参阅Spring Framework文档。
5.13. Support Classes
Package org.springframework.data.redis.support提供了各种可重用的组件,这些组件依赖于Redis作为后备存储。 目前,该软件包在Redis之上包含各种基于JDK的接口实现,例如原子计数器和JDK集合。
原子计数器可以轻松地包装Redis Key增量,而集合允许以最小的存储空间或API泄漏轻松管理Redis Key:特别是RedisSet和RedisZSet接口可以轻松访问Redis支持的集合操作,例如交集和联合 而RedisList在Redis之上实现了List,Queue和Deque契约(以及它们的等价的阻塞同胞),将存储作为FIFO(先进先出),LIFO(后进先出)或封顶 采用最少的配置收集:
<?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>
public class AnotherExample {
// injected
private Deque<String> queue;
public void addTag(String tag) {
queue.push(tag);
}
}
如上例所示,消费代码与实际的存储实现是分离的 - 实际上并没有指出在下面使用Redis。 这使得从开发到生产环境变得透明并且极大地提高了可测试性(Redis的实现可以用内存中的代替)。
5.13.1. 支持Spring Cache抽象
Changed in 2.0
Spring Redis通过org.springframework.data.redis.cache包提供了Spring缓存抽象的实现。 要使用Redis作为后台实现,只需将RedisCacheManager添加到您的配置:
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
RedisCacheManager行为可以通过RedisCacheManagerBuilder配置,允许设置默认的RedisCacheConfiguration,事务行为和预定义的缓存。
RedisCacheManager cm = RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultCacheConfig())
.initialCacheConfigurations(singletonMap("predefined", defaultCacheConfig().disableCachingNullValues()))
.transactionAware()
.build();
通过RedisCacheManager创建的RedisCache行为通过RedisCacheConfiguration定义。 该配置允许设置Key到期时间,前缀和RedisSerializer以转换为二进制存储格式和从二进制存储格式转换。 如上所示,RedisCacheManager允许定义每个缓存库上的配置。
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(1))
.disableCachingNullValues();
RedisCacheManager默认使用无锁RedisCacheWriter来读写二进制值。 无锁缓存提高了吞吐量。 缺少入口锁定可能会导致putIfAbsent和clean方法的重叠,非原子命令,因为这些方法需要多个发送到Redis的命令。 锁定副本通过设置显式锁定键并检查是否存在此键来防止命令重叠,这导致了额外的请求和潜在的命令等待时间。
可以选择锁定行为如下:
RedisCacheManager cm = RedisCacheManager.build(RedisCacheWriter.lockingRedisCacheWriter())
.cacheDefaults(defaultCacheConfig())
...
表4. RedisCacheManager默认值
Setting | Value |
---|---|
Cache Writer | non locking |
Cache Configuration | RedisCacheConfiguration#defaultConfiguration |
Initial Caches | none |
Trasaction Aware | no |
表5. RedisCacheConfiguration默认值
Key Expiration | none |
---|---|
Cachenull |
yes |
Prefix Keys | yes |
Default Prefix | the actual cache name |
Key Serializer | StringRedisSerializer |
Value Serializer | JdkSerializationRedisSerializer |
Conversion Service | DefaultFormattingConversionService with default cache key converters |