参考文档: https://docs.spring.io/spring-data/redis/docs/2.0.3.RELEASE/reference/html/
Redis中文教程: http://www.redis.net.cn/tutorial/3501.html
5.10. Redis Transactions
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将使用它的value, hash key 序列化器来反序列化exec的所有结果。 还有一个额外的exec方法,允许您为事务结果传递自定义序列化程序。
版本1.1中的RedisConnection和RedisTemplate的exec方法已经作了重大更改。 以前,这些方法直接从connectors返回事务结果。 这意味着数据类型通常不同于RedisConnection方法返回的数据类型。 例如,zAdd返回一个boolean,表明该元素已被添加到sorted set中。 大多数connectors将这个值作为一个long值返回,并且Spring Data Redis执行转换。 另一个常见的区别是大多数连接器都会返回一个状态回复(通常是字符串“OK”),以执行set等操作。 这些回复通常被Spring Data Redis丢弃。 在1.1之前,这些转换不是对exec的结果执行的。此外,结果并未在RedisTemplate中反序列化,因此它们通常包含原始字节数组。 如果此更改中断了您的应用程序,可以在RedisConnectionFactory上将convertPipelineAndTxResults设置为false以禁用此行为。
5.10.1. @Transactional Support
事务Transaction的功能默认是禁用的,必须通过设置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为管道提供支持,其中包括向服务器发送多条命令,而无需等待答复,然后在一个单独的步骤中读取答复。当您需要连续发送多个命令时,管道传输可以提高性能,例如将多个元素添加到同一个List列表中。
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;
}
});
上面的例子执行的是批量右侧弹出管道队列中的数据元素。结果List列表包含所有弹出的元素。在返回结果之前,RedisTemplate使用其hash-value, hash-key和hash-value-serializers对所有结果进行反序列化,因此上面示例中返回的元素将是Strings。 还有一些executePipelined方法允许您为管道结果传递自定义序列化程序。
请注意,从RedisCallback返回的值必须为空,因为此值将被丢弃,以便返回管道命令的结果。
版本1.1中对RedisConnection的closePipeline方法做出了重大更改。 此前,此方法直接从connectors返回流水线操作的结果。 这意味着数据类型通常与RedisConnection方法返回的数据类型不同。 例如,zAdd返回一个布尔值,表明该元素已被添加到已排序的集合中。 大多数连接器将这个值作为一个long值返回,并且Spring Data Redis执行转换。 另一个常见的区别是大多数连接器都会返回一个状态回复(通常是字符串“OK”),以执行set等操作。 这些回复通常被Spring Data Redis丢弃。 在1.1之前,这些转换不是对closePipeline的结果执行的。如果此更改中断了您的应用程序,则可以在RedisConnectionFactory上将convertPipelineAndTxResults设置为false以禁用此行为。5.12. Redis Scripting
脚本可以通过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”),它也可以为空。 在应用程序上下文中配置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密钥增量,而集合允许以最小的存储空间或API泄漏轻松管理Redis密钥:特别是RedisSet和RedisZSet接口可以轻松访问Redis支持的集合操作,例如交集intersection
和联合union
,而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. Support for Spring Cache Abstraction - 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定义。该配置允许设置密钥到期时间,前缀和RedisSerializer以转换为二进制存储格式和从二进制存储格式转换。 如上所示,RedisCacheManager允许定义每个缓存库上的配置。
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(1))
.disableCachingNullValues();
RedisCacheManager默认使用无锁 Lock-free RedisCacheWriter来读取和写入二进制值。无锁缓存提高了吞吐量。 缺少入口锁定可能会导致putIfAbsent和clean方法的重叠非原子命令,因为那些方法需要将多个命令发送到Redis。 锁定副本通过设置显式锁定键explicit lock key并检查是否存在此键来防止命令重叠,这会导致额外的请求和潜在的命令等待时间。
可以选择加入锁定行为如下:
RedisCacheManager cm = RedisCacheManager.build(RedisCacheWriter.lockingRedisCacheWriter())
.cacheDefaults(defaultCacheConfig())
...
Setting | Value |
---|---|
Cache Writer | non locking |
Cache Configuration |
|
Initial Caches | none |
Trasaction Aware | no |
Key Expiration | none |
---|---|
Cache | yes |
Prefix Keys | yes |
Default Prefix | the actual cache name |
Key Serializer |
|
Value Serializer |
|
Conversion Service |
|