Redis 从入门到精通【应用篇】之RedisTemplate详解
文章目录
- Redis 从入门到精通【应用篇】之RedisTemplate详解
- 0. 前言
- 1. RedisTemplate 方法
- 1. 设置RedisTemplate的序列化方式
- 2. RedisTemplate的基本操作
- 2. 源码浅析
- 2.1. 构造方法
- 2.2. 序列化方式
- 2.3. RedisTemplate的操作方法
- 2.4. RedisTemplate的事务
- 2.5. RedisTemplate的执行方法
- 2.6. RedisTemplate的回调方法
- 代码示例
- 3.总结
- 3.1. 项目中如何使用 RedisTemplate 支持多个 Redis 数据库?
- 3.2. 如何使用 RedisTemplate 支持 Redis 集群?
- 3.3. 如何使用 RedisTemplate 实现 Redis 事务的乐观锁?
- 3.4. 如何使用 RedisTemplate 实现 Redis 的分布式锁重入?
- 3.5. 如何使用 RedisTemplate 实现 Redis 的分布式事务?
- 3.6. 如何使用 RedisTemplate 实现 Redis 的分布式限速?
- 3.7. 如何使用 RedisTemplate 实现 Redis 的分布式锁可重入性?
- 3.8. 如何使用 RedisTemplate 实现 Redis 的分布式信号量?
- 3.9. 如何使用 RedisTemplate 实现 Redis 的分布式缓存穿透?
- 3.10. 如何使用 RedisTemplate 实现 Redis 的分布式缓存击穿?
- 4. Redis从入门到精通系列文章
0. 前言
在SpringBoot中,可以使用RedisTemplate来操作Redis数据库。RedisTemplate是Spring Data Redis提供的一个强大的Redis客户端,它支持各种Redis数据结构,并提供了许多方便的方法来操作这些数据结构。下面是一些RedisTemplate的用法示例:
1. RedisTemplate 方法
1. 设置RedisTemplate的序列化方式
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);// 设置key和value的序列化方式redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));// 设置hash key和value的序列化方式redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));return redisTemplate;}
}
在此示例中,创建了一个RedisTemplate对象,并设置了key和value的序列化方式为StringRedisSerializer和Jackson2JsonRedisSerializer。同时,还设置了hash key和value的序列化方式。
2. RedisTemplate的基本操作
其实在项目中我们通常会将RedisTemplate 再封装一层,作为一个Redis操作类处理,相当于提供了一层语法糖。
@Service
public class RedisService {// RedisTemplate是Spring提供的对Redis的操作模板类,使用泛型限定key和value的类型为String和Objectprivate final RedisTemplate<String, Object> redisTemplate;// 构造函数,注入RedisTemplate对象public RedisService(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}// 设置key-value键值对public void set(String key, Object value) {redisTemplate.opsForValue().set(key, value);}// 根据key获取valuepublic Object get(String key) {return redisTemplate.opsForValue().get(key);}// 根据key删除键值对public void delete(String key) {redisTemplate.delete(key);}// 判断key是否存在public boolean exists(String key) {return redisTemplate.hasKey(key);}// 将key对应的value增加deltapublic long increment(String key, long delta) {return redisTemplate.opsForValue().increment(key, delta);}// 获取hash结构中所有键值对public Map<Object, Object> hashGetAll(String key) {return redisTemplate.opsForHash().entries(key);}// 向hash结构中添加键值对public void hashPut(String key, Object hashKey, Object value) {redisTemplate.opsForHash().put(key, hashKey, value);}// 根据hash结构中的key获取对应的valuepublic Object hashGet(String key, Object hashKey) {return redisTemplate.opsForHash().get(key, hashKey);}// 根据hash结构中的key删除对应的hashKeypublic void hashDelete(String key, Object... hashKeys) {redisTemplate.opsForHash().delete(key, hashKeys);}// 判断hash结构中是否存在hashKeypublic boolean hashExists(String key, Object hashKey) {return redisTemplate.opsForHash().hasKey(key, hashKey);}// 获取set结构中所有元素public Set<Object> setGetAll(String key) {return redisTemplate.opsForSet().members(key);}// 向set结构中添加元素public void setAdd(String key, Object... values) {redisTemplate.opsForSet().add(key, values);}// 判断set结构中是否存在某个元素public boolean setExists(String key, Object value) {return redisTemplate.opsForSet().isMember(key, value);}// 根据value删除set结构中的元素public void setDelete(String key, Object... values) {redisTemplate.opsForSet().remove(key, values);}// 获取list结构中所有元素public List<Object> listGetAll(String key) {return redisTemplate.opsForList().range(key, 0, -1);}// 向list结构中左侧插入元素public void listPush(String key, Object value) {redisTemplate.opsForList().leftPush(key, value);}// 从list结构中左侧弹出元素public Object listPop(String key) {return redisTemplate.opsForList().leftPop(key);}// 获取list结构中元素的数量public long listSize(String key) {return redisTemplate.opsForList().size(key);}
}
2. 源码浅析
2.1. 构造方法
RedisTemplate的构造方法需要一个RedisConnectionFactory对象作为参数,它通过这个对象来获取Redis连接。RedisConnectionFactory 是 Spring Data Redis 提供的一个接口,用于创建和管理 Redis 连接。它是将 Redis 连接池(连接 Redis 数据库的客户端)与 Spring 应用程序集成的关键。
public RedisTemplate() {RedisConnectionFactory redisConnectionFactory = RedisConnectionConfiguration.determineConnectionFactory(redisSentinelConfiguration, redisClusterConfiguration, connectionFactory, jedisConnectionFactory);setConnectionFactory(redisConnectionFactory);afterPropertiesSet();
}
2.2. 序列化方式
RedisTemplate支持各种数据类型的序列化和反序列化,它提供了以下四种序列化方式:
- keySerializer:key的序列化方式。
- valueSerializer:value的序列化方式。
- hashKeySerializer:hash key的序列化方式。
- hashValueSerializer:hash value的序列化方式。
RedisTemplate默认使用JdkSerializationRedisSerializer作为序列化方式,但是在实际使用中,通常需要根据实际情况选择更加高效的序列化方式,如StringRedisSerializer、Jackson2JsonRedisSerializer等。
public void setKeySerializer(RedisSerializer<?> keySerializer) {Assert.notNull(keySerializer, "RedisSerializer must not be null!");this.keySerializer = keySerializer;
}public void setValueSerializer(RedisSerializer<?> valueSerializer) {Assert.notNull(valueSerializer, "RedisSerializer must not be null!");this.valueSerializer = valueSerializer;
}public void setHashKeySerializer(RedisSerializer<?> hashKeySerializer) {Assert.notNull(hashKeySerializer, "RedisSerializer must not be null!");this.hashKeySerializer = hashKeySerializer;
}public void setHashValueSerializer(RedisSerializer<?> hashValueSerializer) {Assert.notNull(hashValueSerializer, "RedisSerializer must not be null!");this.hashValueSerializer = hashValueSerializer;
}
2.3. RedisTemplate的操作方法
RedisTemplate提供了各种操作方法,如opsForValue()、opsForList()、opsForSet()、opsForZSet()、opsForHash()等,它们返回的是具体数据结构的操作对象,如ValueOperations、ListOperations、SetOperations、ZSetOperations、HashOperations等。这些操作对象提供了各种操作方法,如get()、set()、push()、pop()、add()、remove()、score()、range()、increment()等,它们对应了Redis的各种操作。
public ValueOperations<K, V> opsForValue() {if (valueOps == null) {valueOps = new DefaultValueOperations<>(this);}return valueOps;
}public ListOperations<K, V> opsForList() {if (listOps == null) {listOps = new DefaultListOperations<>(this);}return listOps;
}public SetOperations<K, V> opsForSet() {if (setOps == null) {setOps = new DefaultSetOperations<>(this);}return setOps;
}public ZSetOperations<K, V> opsForZSet() {if (zSetOps == null) {zSetOps = new DefaultZSetOperations<>(this);}return zSetOps;
}public HashOperations<K, HK, HV> opsForHash() {if (hashOps == null) {hashOps = new DefaultHashOperations<>(this);}return hashOps;
}
2.4. RedisTemplate的事务
RedisTemplate支持事务,它提供了multi()、exec()和discard()三个方法来实现事务。multi()方法用于开启事务,exec()方法用于提交事务,discard()方法用于回滚事务。
public void multi() {RedisConnectionUtils.bindConnection(getRequiredConnectionFactory(), true);try {RedisConnectionUtils.getRequiredConnection(getConnectionFactory()).multi();} catch (RuntimeException ex) {RedisConnectionUtils.unbindConnection(getRequiredConnectionFactory());throw ex;}
}public List<Object> exec() {RedisConnectionUtils.unbindConnectionIfPossible(getConnectionFactory());return execute((RedisCallback<List<Object>>) connection -> {List<Object> results = connection.exec();return results != null ? results : Collections.emptyList();}, true);
}public void discard() {RedisConnectionUtils.unbindConnectionIfPossible(getConnectionFactory());execute(RedisConnectionUtils::discard, true);
}
2.5. RedisTemplate的执行方法
RedisTemplate提供了execute()方法来执行Redis操作,它需要传入一个RedisCallback对象作为参数,RedisCallback是一个函数式接口,它定义了一个回调函数,用于执行具体的Redis操作。execute()方法会获取一个Redis连接,执行RedisCallback对象的回调函数,并返回回调函数的结果。
public <T> T execute(RedisCallback<T> action, boolean exposeConnection) {Assert.notNull(action, "Callback object must not be null");RedisConnection conn = null;try {conn = getConnection(exposeConnection);boolean existingConnection = TransactionSynchronizationManager.hasResource(getConnectionFactory());RedisConnection connToUse = preProcessConnection(conn, existingConnection);T result = action.doInRedis(connToUse);return postProcessResult(result, conn, existingConnection);} catch (RuntimeException ex) {releaseConnection(conn, existingConnection);throw ex;} finally {if (!exposeConnection) {RedisConnectionUtils.releaseConnection(conn, getConnectionFactory(), false);}}
}
在execute()方法中,首先获取Redis连接,然后调用preProcessConnection()方法进行预处理,接着执行RedisCallback对象的回调函数,最后调用postProcessResult()方法进行后处理,并返回结果。如果执行过程中发生异常,会调用releaseConnection()方法释放Redis连接。
2.6. RedisTemplate的回调方法
RedisTemplate的回调方法主要有以下三个:
这里有三个方法:
-
execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline)
:执行Redis操作的核心方法。接受一个RedisCallback对象,该对象封装了要在Redis上执行的操作。还有两个布尔类型的参数,一个表示是否暴露连接(exposeConnection),另一个表示是否启用pipeline(pipeline)。如果启用pipeline,将使用Redis连接对象的openPipeline()和closePipeline()方法执行操作。 -
execute(SessionCallback<T> session)
:执行Redis事务的方法。接受一个SessionCallback对象,该对象封装了在 Redis 事务中执行的操作。该方法会绑定 Redis 连接并执行 SessionCallback 对象的 execute() 方法,最后解除绑定。 -
executePipelined(SessionCallback<?> session, @Nullable RedisSerializer<?> resultSerializer)
:执行 Redis pipeline 的方法。接受一个SessionCallback对象,该对象封装了要在 Redis pipeline 中执行的操作,以及一个可选的 RedisSerializer 对象,用于反序列化结果。该方法会绑定 Redis 连接并执行 SessionCallback 对象的 execute() 方法,在执行期间使用 Redis 连接对象的 openPipeline() 和 closePipeline() 方法开启和关闭 Redis pipeline。
常用场景:第一个方法执行单个Redis操作,第二个方法执行Redis事务,第三个方法执行Redis
pipeline。此外,第二个方法是为了执行多个 Redis 操作而设计的,而第一个方法和第三个方法只执行单个 Redis 操作。第三个方法需要额外的参数,用于反序列化 Redis pipeline 的结果。
@Nullablepublic <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");Assert.notNull(action, "Callback object must not be null");RedisConnectionFactory factory = this.getRequiredConnectionFactory();RedisConnection conn = RedisConnectionUtils.getConnection(factory, this.enableTransactionSupport);Object var11;try {boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);RedisConnection connToUse = this.preProcessConnection(conn, existingConnection);boolean pipelineStatus = connToUse.isPipelined();if (pipeline && !pipelineStatus) {connToUse.openPipeline();}RedisConnection connToExpose = exposeConnection ? connToUse : this.createRedisConnectionProxy(connToUse);T result = action.doInRedis(connToExpose);if (pipeline && !pipelineStatus) {connToUse.closePipeline();}var11 = this.postProcessResult(result, connToUse, existingConnection);} finally {RedisConnectionUtils.releaseConnection(conn, factory, this.enableTransactionSupport);}return var11;}public <T> T execute(SessionCallback<T> session) {Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");Assert.notNull(session, "Callback object must not be null");RedisConnectionFactory factory = this.getRequiredConnectionFactory();RedisConnectionUtils.bindConnection(factory, this.enableTransactionSupport);Object var3;try {var3 = session.execute(this);} finally {RedisConnectionUtils.unbindConnection(factory);}return var3;}public List<Object> executePipelined(SessionCallback<?> session) {return this.executePipelined(session, this.valueSerializer);}public List<Object> executePipelined(SessionCallback<?> session, @Nullable RedisSerializer<?> resultSerializer) {Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");Assert.notNull(session, "Callback object must not be null");RedisConnectionFactory factory = this.getRequiredConnectionFactory();RedisConnectionUtils.bindConnection(factory, this.enableTransactionSupport);List var4;try {var4 = (List)this.execute((connection) -> {connection.openPipeline();boolean pipelinedClosed = false;List var7;try {Object result = this.executeSession(session);if (result != null) {throw new InvalidDataAccessApiUsageException("Callback cannot return a non-null value as it gets overwritten by the pipeline");}List<Object> closePipeline = connection.closePipeline();pipelinedClosed = true;var7 = this.deserializeMixedResults(closePipeline, resultSerializer, this.hashKeySerializer, this.hashValueSerializer);} finally {if (!pipelinedClosed) {connection.closePipeline();}}return var7;});} finally {RedisConnectionUtils.unbindConnection(factory);}return var4;}
代码示例
- 使用
execute(RedisCallback<T> action)
执行 Redis 命令
// 使用 execute() 方法执行 Redis 命令
String key = "myKey";
String value = redisTemplate.execute((RedisCallback<String>) connection -> {connection.set(redisTemplate.getStringSerializer().serialize(key), redisTemplate.getStringSerializer().serialize("Hello"));return redisTemplate.getStringSerializer().deserialize(connection.get(redisTemplate.getStringSerializer().serialize(key)));
});// 输出 Redis 命令执行结果
System.out.println(value); // 输出 "Hello"
在上面的示例中, 我们使用 execute(RedisCallback<T> action)
方法将 Redis 命令封装在一个 RedisCallback
对象中,并将其传递给 execute()
方法。该命令使用 set()
方法将一个 key-value 对写入 Redis 中,然后使用 get()
方法从 Redis 中读取该 key 对应的值。
- 使用
execute(SessionCallback<T> session)
执行 Redis 事务
// 使用 execute(SessionCallback<T> session) 方法执行 Redis 事务
String key1 = "myKey1";
String key2 = "myKey2";
String value1 = "myValue1";
String value2 = "myValue2";
List<Object> results = redisTemplate.execute((SessionCallback<List<Object>>) session -> {session.multi();session.opsForValue().set(key1, value1);session.opsForValue().set(key2, value2);return session.exec();
});// 输出 Redis 事务的结果
System.out.println(results); // 输出 "[true, true]"
使用 execute(SessionCallback<T> session)
方法将 Redis 事务封装在一个 SessionCallback<T>
对象中,并将其传递给 execute()
方法。该事务使用 multi()
方法开启事务,在事务中使用 opsForValue()
对象的 set()
方法将两个 key-value 对写入 Redis 中,最后使用 exec()
方法提交事务。事务执行完成后,我们可以通过 execute()
方法返回的结果列表查看每个 Redis 命令的执行结果。在上面的示例中,我们可以看到结果列表为 [true, true]
,表示两个 Redis 命令都成功执行。
- 使用
executePipelined(SessionCallback<?> session)
执行 Redis pipeline
// 使用 executePipelined(SessionCallback<?> session) 方法执行 Redis pipeline
String key1 = "myKey1";
String key2 = "myKey2";
List<Object> results = redisTemplate.executePipelined((SessionCallback<List<Object>>) session -> {session.opsForValue().get(key1);session.opsForValue().get(key2);return null;
});// 输出 Redis pipeline 的结果
System.out.println(results); // 输出 "[Hello1, Hello2]"
使用 executePipelined(SessionCallback<?> session)
方法将 Redis pipeline 封装在一个 SessionCallback<?>
对象中,并将其传递给 executePipelined()
方法。该 pipeline 使用 opsForValue()
对象的 get()
方法获取两个 key 的值,并返回一个结果列表。在执行期间,该方法将使用 Redis 连接对象的 openPipeline()
和 closePipeline()
方法开启和关闭 Redis pipeline,以便可以批量执行多个命令。
3.总结
看完这些基本上行只是学会了增删查看和批量操作。也就是只学会了RedisTemplate 的皮毛。其实在项目中还有更多的复杂需求,需要重新实现。
比如以下这些问题,也是很常见的,需要我们处理实际的问题。我大概做一个简答,后面将详细输出示例
3.1. 项目中如何使用 RedisTemplate 支持多个 Redis 数据库?
可以通过配置 RedisConnectionFactory 来支持多个 Redis 数据库,其中可以使用 JedisConnectionFactory 或 LettuceConnectionFactory 来创建不同的 RedisConnectionFactory 实例。详细教程可以参考我的其他博客
《SpringBoot 项目配置多数据源》
3.2. 如何使用 RedisTemplate 支持 Redis 集群?
可以使用 RedisTemplate 的 ClusterRedisConnectionFactory 来支持 Redis 集群,通过配置 Redis 集群中的多个节点来实现高可用性和负载均衡。详细教程参考我的其他博客
《SpringBoot 项目配置 Redis 集群》
3.3. 如何使用 RedisTemplate 实现 Redis 事务的乐观锁?
可以使用 RedisTemplate 的 watch() 方法和 multi() 方法来实现 Redis 事务的乐观锁,通过在事务开始前使用 watch() 方法监视 Redis 中的某个 key,然后在事务中使用 multi() 方法执行多个 Redis 命令,并使用 exec() 方法提交事务,如果在事务执行期间,被监视的 key 被修改,则事务会失败,从而实现乐观锁。
详细教程可以参考我的其他博客
《SpringBoot 项目配置 Redis 集群》
3.4. 如何使用 RedisTemplate 实现 Redis 的分布式锁重入?
可以使用 RedisTemplate 的 ThreadLocal 方式来实现 Redis 的分布式锁重入,即在每个线程中保存一个 Redis 分布式锁的状态,并在需要重入时,检查当前线程是否已经获取了分布式锁。
3.5. 如何使用 RedisTemplate 实现 Redis 的分布式事务?
可以使用 RedisTemplate 的 execute(SessionCallback session) 方法来实现 Redis 的分布式事务,其中 SessionCallback 接口可以用来执行多个 Redis 命令,并保证这些命令以原子方式执行。
3.6. 如何使用 RedisTemplate 实现 Redis 的分布式限速?
可以使用 RedisTemplate 的 incr() 方法和 expire() 方法来实现 Redis 的分布式限速,通过在 Redis 中设置一个计数器和过期时间来实现分布式限速。
3.7. 如何使用 RedisTemplate 实现 Redis 的分布式锁可重入性?
可以使用 RedisTemplate 的 ReentrantRedisLock 类来实现 Redis 的分布式锁可重入性,该类可以在 Redis 中保存一个计数器来记录锁的重入次数,并在释放锁时,检查当前线程是否已经完全释放了锁,从而实现分布式锁的可重入性。
3.8. 如何使用 RedisTemplate 实现 Redis 的分布式信号量?
可以使用 RedisTemplate 的 RedisSemaphore 类来实现 Redis 的分布式信号量,该类可以在 Redis 中保存一个计数器来记录当前已经获得信号量的数量,并在释放信号量时,将计数器减一。
3.9. 如何使用 RedisTemplate 实现 Redis 的分布式缓存穿透?
可以使用 RedisTemplate 的缓存注解(例如 @Cacheable、@CachePut、@CacheEvict)和布隆过滤器来实现 Redis 的分布式缓存穿透,其中布隆过滤器可以用来过滤掉不存在的 key,从而避免缓存穿透的问题。
3.10. 如何使用 RedisTemplate 实现 Redis 的分布式缓存击穿?
可以使用 RedisTemplate 的缓存注解(例如 @Cacheable、@CachePut、@CacheEvict)和 Redis 分布式锁来实现 Redis 的分布式缓存击穿,其中 Redis 分布式锁可以用来防止缓存击穿,即在缓存不存在的情况下,使用 Redis 分布式锁来避免多个线程同时访问数据库。
4. Redis从入门到精通系列文章
- 《Redis【应用篇】之RedisTemplate基本操作》
- 《Redis 从入门到精通【实践篇】之SpringBoot配置Redis多数据源》
- 《Redis 从入门到精通【进阶篇】之三分钟了解Redis HyperLogLog 数据结构》
- 《Redis 从入门到精通【进阶篇】之三分钟了解Redis地理位置数据结构GeoHash》
- 《Redis 从入门到精通【进阶篇】之高可用哨兵机制(Redis Sentinel)详解》
- 《Redis 从入门到精通【进阶篇】之redis主从复制详解》
- 《Redis 从入门到精通【进阶篇】之Redis事务详解》
- 《Redis从入门到精通【进阶篇】之对象机制详解》
- 《Redis从入门到精通【进阶篇】之消息传递发布订阅模式详解》
- 《Redis从入门到精通【进阶篇】之持久化 AOF详解》
- 《Redis从入门到精通【进阶篇】之持久化RDB详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
- 《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
- 《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》
大家好,我是冰点,今天的Redis【实践篇】之RedisTemplate基本操作详解,全部内容就是这些。如果你有疑问或见解可以在评论区留言。