RedisTemplate 提供了丰富的方法来实现对 Redis 的各种操作,包括但不限于字符串、哈希、列表、集合和有序集合等数据结构的操作。以下是一些常用的 RedisTemplate API:
一、字符串操作
opsForValue().set(key, value)
: 设置字符串值。opsForValue().get(key)
: 获取字符串值。opsForValue().incr(key)
: 字符串值自增。opsForValue().decr(key)
: 字符串值自减。
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testString(){
String key = "string-name";
//存
String value = "这是value123";
stringRedisTemplate.opsForValue().set(key,value);
//取
Object valueObj = stringRedisTemplate.opsForValue().get(key);
System.out.println("value为:" + valueObj);
}
import com.alibaba.fastjson.JSON;
import org.example.Main;
import org.example.dto.UserDTO;
import org.example.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testUser(){
String key = "user-key";
//存
UserDTO userDTO = new UserDTO();
userDTO.setUserAccount("zhangsan");
userDTO.setAge(18);
userDTO.setUserName("张三");
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(userDTO));
//取
String valueObj = stringRedisTemplate.opsForValue().get(key);
UserDTO redisUser = JSON.parseObject(valueObj, UserDTO.class);
System.out.println("value为:" + redisUser);
}
二、哈希操作
opsForHash().getOperations().put(key, hashKey, value): 向哈希中添加键值对。
opsForHash().getOperations().get(key, hashKey): 获取哈希中的值。
opsForHash().getOperations().entries(key): 获取哈希中的所有键值对。
三、列表操作
opsForList().leftPush(key, value): 从列表左侧添加元素。
opsForList().rightPush(key, value): 从列表右侧添加元素。
opsForList().leftPop(key): 从列表左侧弹出元素。
opsForList().rightPop(key): 从列表右侧弹出元素。
四、集合操作
opsForSet().add(key, value)
: 向集合中添加元素。opsForSet().members(key)
: 获取集合中的所有元素。opsForSet().remove(key, value)
: 从集合中移除元素。
五、有序集合操作
opsForZSet().add(key, value, score): 向有序集合中添加元素,并指定分数。
opsForZSet().range(key, start, end): 获取有序集合中指定分数范围内的元素。
opsForZSet().removeRangeByScore(key, minScore, maxScore): 按分数范围移除有序集合中的元素。
六、键操作
delete(key)
: 删除键。hasKey(key)
: 检查键是否存在。keys(pattern)
: 根据模式匹配获取所有键。
七、执行命令(事务操作)
- execute
():
用于在 Redis 连接上执行单个 Redis 命令,并返回执行命令后的结果。该方法执行完后会自动关闭 Redis 连接。 - executePipelined
():
相比之下,executePipelined() 方法用于一次性在 Redis 连接上执行多个 Redis 命令,使用管道(pipeline)方式进行优化,减少网络 I/O 开销。与 execute() 方法不同的是,executePipelined() 方法返回的是一个 Redis 命令结果列表,而不是单个命令的结果对象。在执行多个 Redis 命令时,可以使用 executePipelined() 方法来提高性能和效率。
当数据量较小时,使用 RedisCallback 和 SessionCallback 性能表现相近是可以理解的,因为此时 Redis 执行单个命令的时间较短,差异不大。但是当数据量增多时,SessionCallback 的优势会更加明显,因为当有多个 Redis 命令需要保持一致性时,使用 RedisCallback 会在执行每个 Redis 命令时都进行连接的获取和释放,而使用 SessionCallback 则可以通过事务管理器提供的 Redis 连接来执行多个 Redis 命令,减少了连接的获取和释放次数,从而提高了性能。
对比维度 | execute() | executePipelined() |
---|---|---|
执行方式 | 逐个执行 Redis 命令 | 将多个 Redis 命令封装在管道内,一次性提交 |
返回结果 | 根据具体 Redis 命令返回不同类型的结果 | 返回多个 Redis 命令的执行结果列表 |
执行效率 | 较慢,需要频繁的连接获取和释放 | 快,将多个 Redis 命令封装在一个管道内,减少连接数 |
使用场景 | 需要手动管理 Redis 连接或者执行单个 Redis 命令时 | 需要高性能地执行多个 Redis 命令时 |
this.stringRedisTemplate.execute(
(RedisCallback<Long>)
(connection) -> {
return connection.del(keysByte);
});
如删除key 除了可以使用RedisTemplate本身的清除方法,还可以通过execute方法执行命令,借助
DefaultedRedisConnection来操作,
public Boolean expire(@NonNull String key, long timeout, @NonNull TimeUnit unit) {
// return this.template.expire(key, timeout, timeUnit);
byte[] rawKey = rawKey(key);
long rawTimeout = TimeoutUtils.toMillis(timeout, unit);
return this.template.execute(connection -> {
try {
return connection.pExpire(rawKey, rawTimeout);
} catch (Exception e) {
// Driver may not support pExpire or we may be running on Redis 2.4
return connection.expire(rawKey, TimeoutUtils.toSeconds(timeout, unit));
}
}, true);
}
public Boolean delete(@NonNull String key) {
byte[] rawKey = rawKey(key);
Long result = this.template.execute(connection -> connection.del(rawKey), true);
return result != null && result.intValue() == 1;
}
private byte[] rawKey(String key) {
Assert.notNull(key, "non null key required");
StringRedisSerializer keySerializer = (StringRedisSerializer) getKeySerializer();
return keySerializer.serialize(key);
}
八、广播发布/订阅
1、使用方法
两个API:
(1)发送消息:
convertAndSend(channel, message)
: 发布消息。
stringRedisTemplate.convertAndSend
(2)接收消息:
subscribe(RedisMessageListenerContainer, MessageListener)
: 订阅消息。
① implements MessageListener来实现一个监听器;
② 注册监听器到RedisMessageListenerContainer中。
2、demo
(1)监听器:如我有两个监听器
package org.example.listen;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.example.dto.UserDTO;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class RedisBroadcastUserMsgHandler implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String consumerChannel = RedisSerializer.string().deserialize(message.getChannel());
if(!consumerChannel.equals("user-topic")){
return;
}
byte[] body = message.getBody();
String json = RedisSerializer.string().deserialize(body);
UserDTO userDTO = JSON.parseObject(json,UserDTO.class);
log.info("handle接收到:"+userDTO);
}
}
package org.example.listen;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.example.dto.UserDTO;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class RedisBroadcastUserMsgTwoHandler implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
String consumerChannel = RedisSerializer.string().deserialize(message.getChannel());
if(!consumerChannel.equals("user-topic-two")){
return;
}
byte[] body = message.getBody();
String json = RedisSerializer.string().deserialize(body);
UserDTO userDTO = JSON.parseObject(json,UserDTO.class);
log.info("handle接收到:"+userDTO);
}
}
(2)注册监听器
package org.example.config;
import lombok.extern.slf4j.Slf4j;
import org.example.listen.RedisBroadcastUserMsgHandler;
import org.example.listen.RedisBroadcastUserMsgTwoHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import java.util.List;
@Slf4j
@Configuration
public class RedisListenerConfig {
@Autowired
private RedisBroadcastUserMsgHandler userMsgHandler;
@Autowired
private RedisBroadcastUserMsgTwoHandler twoHandler;
@Bean
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(userMsgHandler, new ChannelTopic("user-topic"));
container.addMessageListener(twoHandler, new ChannelTopic("user-topic-two"));
return container;
}
}
(3)发送消息
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testSendMsg(){
UserDTO userDTO = new UserDTO();
userDTO.setUserAccount("zhangsan");
userDTO.setAge(18);
userDTO.setUserName("张三");
String channel = "user-topic";
stringRedisTemplate.convertAndSend(channel,JSON.toJSONString(userDTO));
//
String channelTwo = "user-topic-two";
stringRedisTemplate.convertAndSend(channelTwo,JSON.toJSONString(userDTO));
}
执行控制台打印:
2024-08-13T16:04:31.630+08:00 INFO 10496 --- [edisContainer-2] o.e.l.RedisBroadcastUserMsgTwoHandler : handle接收到:UserDTO(userName=张三, age=18, userAccount=zhangsan)
2024-08-13T16:04:31.630+08:00 INFO 10496 --- [edisContainer-1] o.e.listen.RedisBroadcastUserMsgHandler : handle接收到:UserDTO(userName=张三, age=18, userAccount=zhangsan)
3、改进demo
如果有多个消费者,注册会比较麻烦,因此对上述监听器代码封装下:
(1)监听器:
package org.example.listen;
import org.springframework.data.redis.connection.MessageListener;
public abstract class RedisListener implements MessageListener {
public abstract String getTopic();
}
package org.example.listen;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.example.dto.UserDTO;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class RedisBroadcastUserMsgHandler extends RedisListener {
@Override
public String getTopic() {
return "user-topic";
}
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] body = message.getBody();
String json = RedisSerializer.string().deserialize(body);
UserDTO userDTO = JSON.parseObject(json,UserDTO.class);
log.info("handle接收到:"+userDTO);
}
}
package org.example.listen;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.example.dto.UserDTO;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class RedisBroadcastUserMsgTwoHandler extends RedisListener {
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] body = message.getBody();
String json = RedisSerializer.string().deserialize(body);
UserDTO userDTO = JSON.parseObject(json,UserDTO.class);
log.info("handle接收到:"+userDTO);
}
@Override
public String getTopic() {
return "user-topic-two";
}
}
(2)注册监听器:
package org.example.config;
import lombok.extern.slf4j.Slf4j;
import org.example.listen.RedisBroadcastUserMsgHandler;
import org.example.listen.RedisBroadcastUserMsgTwoHandler;
import org.example.listen.RedisListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import java.util.List;
@Slf4j
@Configuration
public class RedisListenerConfig {
@Autowired
private RedisBroadcastUserMsgHandler userMsgHandler;
@Autowired
private List<RedisListener> redisListeners;
@Bean
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
for(RedisListener listener : redisListeners) {
container.addMessageListener(listener, new ChannelTopic(listener.getTopic()));
}
return container;
}
}
执行Test:
@Test
public void testSendMsg(){
UserDTO userDTO = new UserDTO();
userDTO.setUserAccount("zhangsan");
userDTO.setAge(18);
userDTO.setUserName("张三");
String channel = "user-topic";
stringRedisTemplate.convertAndSend(channel,JSON.toJSONString(userDTO));
//
userDTO.setUserAccount("zhangsan-two");
userDTO.setUserName("张三-two");
String channelTwo = "user-topic-two";
stringRedisTemplate.convertAndSend(channelTwo,JSON.toJSONString(userDTO));
}
打印:
2024-08-13T16:25:10.725+08:00 INFO 88872 --- [edisContainer-1] o.e.listen.RedisBroadcastUserMsgHandler : handle接收到:UserDTO(userName=张三, age=18, userAccount=zhangsan)
2024-08-13T16:25:10.725+08:00 INFO 88872 --- [edisContainer-2] o.e.l.RedisBroadcastUserMsgTwoHandler : handle接收到:UserDTO(userName=张三-two, age=18, userAccount=zhangsan-two)
九、scan游标
在 Redis 中,SCAN
命令用于迭代数据库中的键值对,以避免一次性加载所有键值对导致的性能问题。SCAN
命令是增量式的,不会一次性返回所有结果,而是通过游标来实现分页。
1、主要步骤说明
(1)设置 ScanOptions
match("xxx")
:限制扫描的键名,模糊匹配。count(100)
:指定每次返回的键的数量,这可以控制扫描的粒度和性能,通常会设置一个合适的数量。
(2)调用接口
① 查询key,调用redisTemplate.executeWithStickyConnection,
可以访问底层的 RedisConnection
,并调用 scan
命令来执行扫描操作。
② 根据key查询value,如根据key查询hash,
stringRedisTemplate.opsForHash().scan(key, options);
(3)遍历扫描结果
使用 Cursor
对象遍历结果集,直到没有更多的键可以扫描为止。
(4)关闭游标
最后,确保扫描结束后关闭游标。
2、特点
(1)增量扫描:SCAN
命令是增量式的,每次扫描会返回一个游标,直到返回的游标为零时,才表示扫描完成。
(2)性能:SCAN
命令并不阻塞,因此它适用于大规模数据的增量扫描。通过调整 COUNT
参数,可以控制每次返回的数据量,从而优化性能。
3、使用场景
SCAN
命令主要用于在 Redis 中增量式地扫描键。它适用于需要遍历大量键而又不希望阻塞 Redis 服务的场景,通常用于以下几种情况:
(1)查找特定模式的键
当你不知道所有的键名,但知道它们符合某个模式时,可以使用 SCAN
来查找这些键。
例如,如果你有很多以 "user:"
开头的键(如 user:123
, user:456
等),你可以通过 SCAN
命令扫描这些键,而不需要一次性加载所有的键。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.Cursor;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class RedisScanService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void scanKeys() {
// 设置扫描选项,这里以匹配所有以 "user:" 开头的键为例
ScanOptions options = ScanOptions.scanOptions().match("user:*").count(100).build();
// 使用 redisTemplate 进行 scan 操作
Cursor<String> cursor = redisTemplate.executeWithStickyConnection(
connection -> connection.scan(options)
);
// 遍历扫描结果
while (cursor.hasNext()) {
String key = cursor.next();
System.out.println("Found key: " + key);
}
// 关闭游标
cursor.close();
}
}
(2)删除特定模式的键
先利用上面的方法查找出所有key,再批量删除即可
public Long delete(Collection<String> keys) {
if (org.springframework.util.CollectionUtils.isEmpty(keys)) {
return 0L;
}
byte[][] rawKeys = rawKeys(keys);
return execute(connection -> connection.del(rawKeys), true);
}
(3)根据key遍历大hash
@Test
public void getHash(){
Map<Object, Object> result = new HashMap<>();
String key = "your hash key";
ScanOptions options = ScanOptions.scanOptions().count(3).build();
Cursor<Map.Entry<Object, Object>> cursor = stringRedisTemplate.opsForHash().scan(key, options);
while (cursor.hasNext()) {
Map.Entry<Object, Object> next = cursor.next();
result.put(next.getKey(), next.getValue());
}
}
十、连接管理
getConnectionFactory()
: 获取连接工厂。getExecutor()
: 获取执行器。
十一、序列化
setKeySerializer(Serializer)
: 设置键的序列化器。setValueSerializer(Serializer)
: 设置值的序列化器。
更多操作可以查看官方提供的API文档:RedisTemplate (Spring Data Redis 3.3.2 API)