Redis高级特性详解
2025/9/17大约 8 分钟
Redis高级特性详解
前置知识
在学习本教程之前,建议您已经掌握:
- Redis 基础知识和核心数据类型
- Spring Boot 与 Redis 的集成
- Java 并发编程基础
Redis 事务
Redis 事务允许在一个步骤中执行一组命令,保证所有命令要么全部执行,要么全部不执行。
1. 事务基本操作
在 Redis 中,事务是通过 MULTI、EXEC、DISCARD 和 WATCH 命令来实现的:
- MULTI:标记事务的开始
- EXEC:执行事务中的所有命令
- DISCARD:取消事务
- WATCH:监视一个或多个键,如果在事务执行前这些键被修改,事务将被打断
2. Spring Boot 中使用事务
@Service
@Slf4j
public class RedisTransactionDemo {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 使用 Redis 事务
*/
public void executeTransaction() {
// 开启事务
stringRedisTemplate.multi();
try {
// 在事务中执行命令
stringRedisTemplate.opsForValue().set("tx:key1", "value1");
stringRedisTemplate.opsForValue().set("tx:key2", "value2");
stringRedisTemplate.opsForValue().increment("tx:counter");
// 提交事务
stringRedisTemplate.exec();
log.info("事务执行成功");
} catch (Exception e) {
// 回滚事务
stringRedisTemplate.discard();
log.error("事务执行失败", e);
}
}
/**
* 使用 WATCH 命令实现乐观锁
*/
public boolean transferMoney(String fromAccount, String toAccount, double amount) {
// 监视账户余额,如果在执行事务前被修改,事务将失败
stringRedisTemplate.watch(fromAccount);
// 获取账户余额
String balanceStr = stringRedisTemplate.opsForValue().get(fromAccount);
double balance = Double.parseDouble(balanceStr != null ? balanceStr : "0");
// 检查余额是否足够
if (balance < amount) {
// 取消监视
stringRedisTemplate.unwatch();
return false;
}
// 开启事务
stringRedisTemplate.multi();
// 在事务中执行转账操作
stringRedisTemplate.opsForValue().set(fromAccount, String.valueOf(balance - amount));
// 获取目标账户余额并增加转账金额
String toBalanceStr = stringRedisTemplate.opsForValue().get(toAccount);
double toBalance = Double.parseDouble(toBalanceStr != null ? toBalanceStr : "0");
stringRedisTemplate.opsForValue().set(toAccount, String.valueOf(toBalance + amount));
// 提交事务
List<Object> results = stringRedisTemplate.exec();
// 如果事务执行成功,results 不为空
return results != null && !results.isEmpty();
}
}
注意事项
Redis 事务不支持回滚操作。如果事务中的某个命令执行失败,其他命令仍会执行。
Redis 管道(Pipeline)
管道允许客户端一次性发送多个命令到服务器,然后一次性接收所有结果,减少网络往返时间。
1. 管道基本使用
@Service
@Slf4j
public class RedisPipelineDemo {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 使用 Pipeline 批量处理命令
*/
public List<Object> executePipeline() {
return stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
// 批量执行命令
for (int i = 0; i < 1000; i++) {
stringRedisConn.set("pipeline:key" + i, "value" + i);
}
// 必须返回 null,否则 executePipelined 方法会覆盖返回值
return null;
}
});
}
/**
* 使用 Lambda 表达式简化代码
*/
public List<Object> executePipelineWithLambda() {
return stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
// 批量执行命令
for (int i = 0; i < 1000; i++) {
stringRedisConn.set("pipeline:key" + i, "value" + i);
}
return null;
});
}
}
2. 事务与管道的区别
区别
- 事务:保证命令的原子性执行,要么全部执行,要么全部不执行
- 管道:仅仅是减少网络往返时间,提高性能,不保证原子性
Redis 发布订阅
Redis 发布订阅是一种消息通信模式,发送者发送消息,订阅者接收消息。
1. 基本概念
- 发布者:发送消息到指定的频道
- 订阅者:订阅一个或多个频道,接收消息
- 频道:消息传递的通道
2. Spring Boot 中使用发布订阅
@Configuration
public class RedisConfig {
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 订阅多个频道
container.addMessageListener(listenerAdapter, new PatternTopic("news:*"));
container.addMessageListener(listenerAdapter, new PatternTopic("chat:*"));
return container;
}
@Bean
public MessageListenerAdapter messageListenerAdapter(RedisMessageReceiver receiver) {
// 指定消息处理方法
return new MessageListenerAdapter(receiver, "receiveMessage");
}
@Bean
public RedisMessageReceiver redisMessageReceiver() {
return new RedisMessageReceiver();
}
}
@Component
@Slf4j
public class RedisMessageReceiver {
/**
* 接收消息的方法
*/
public void receiveMessage(String message, String channel) {
log.info("从频道 {} 接收到消息: {}", channel, message);
// 处理接收到的消息
}
}
@Service
@Slf4j
public class RedisMessagePublisher {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 发布消息到指定频道
*/
public void publishMessage(String channel, String message) {
log.info("发布消息到频道 {}: {}", channel, message);
stringRedisTemplate.convertAndSend(channel, message);
}
}
3. 使用示例
@RestController
@RequestMapping("/redis/pubsub")
public class RedisPubSubController {
@Autowired
private RedisMessagePublisher publisher;
@PostMapping("/publish")
public String publishMessage(
@RequestParam String channel,
@RequestParam String message) {
publisher.publishMessage(channel, message);
return "消息已发布";
}
}
Redis Lua 脚本
Redis 支持使用 Lua 脚本执行原子操作,可以在一个脚本中执行多个命令。
1. Lua 脚本基础
Lua 脚本在 Redis 中的优势:
- 原子性:脚本中的所有命令作为一个整体执行
- 减少网络开销:一次发送多个命令
- 可复用:脚本可以保存并多次执行
2. Spring Boot 中使用 Lua 脚本
@Service
@Slf4j
public class RedisLuaDemo {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 使用 Lua 脚本实现计数器
*/
public Long incrementCounter(String key, long value) {
// Lua 脚本:如果 key 不存在则创建并设置为 0,然后增加指定值
String script =
"local current = redis.call('get', KEYS[1]) " +
"if current == false then " +
" redis.call('set', KEYS[1], 0) " +
" current = 0 " +
"end " +
"local new = tonumber(current) + tonumber(ARGV[1]) " +
"redis.call('set', KEYS[1], new) " +
"return new";
// 创建脚本对象
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
// 执行脚本
return stringRedisTemplate.execute(
redisScript,
Collections.singletonList(key),
String.valueOf(value)
);
}
/**
* 使用 Lua 脚本实现限流器
*/
public boolean isAllowed(String key, long maxRequests, long windowSeconds) {
// Lua 脚本:实现滑动窗口限流
String script =
"local key = KEYS[1] " +
"local max_requests = tonumber(ARGV[1]) " +
"local window_seconds = tonumber(ARGV[2]) " +
"local current_time = redis.call('time')[1] " +
"redis.call('zremrangebyscore', key, 0, current_time - window_seconds) " +
"local count = redis.call('zcard', key) " +
"if count < max_requests then " +
" redis.call('zadd', key, current_time, current_time .. ':' .. math.random()) " +
" redis.call('expire', key, window_seconds) " +
" return 1 " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = stringRedisTemplate.execute(
redisScript,
Collections.singletonList(key),
String.valueOf(maxRequests),
String.valueOf(windowSeconds)
);
return result != null && result == 1L;
}
}
Redis 分布式锁
分布式锁是控制分布式系统中多个进程对共享资源访问的一种方式。
简单分布式锁
RedisLockDemo
@Service
@Slf4j
public class RedisLockDemo {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final String LOCK_PREFIX = "lock:";
private static final long LOCK_EXPIRE = 30000; // 30秒
/**
* 获取锁
*/
public boolean lock(String key, String value) {
String lockKey = LOCK_PREFIX + key;
// 设置锁,成功返回true,失败返回false
return Boolean.TRUE.equals(stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey, value, LOCK_EXPIRE, TimeUnit.MILLISECONDS));
}
/**
* 释放锁
*/
public boolean unlock(String key, String value) {
String lockKey = LOCK_PREFIX + key;
// Lua脚本:判断值一致才删除
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
redisScript.setResultType(Long.class);
Long result = stringRedisTemplate.execute(
redisScript,
Collections.singletonList(lockKey),
value
);
return result != null && result > 0;
}
}
Redisson 实现分布式锁
Redisson 是一个在 Redis 基础上实现的 Java 驻内存数据网格,提供了更完善的分布式锁实现。
<!-- 添加 Redisson 依赖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>
RedissonLockDemo
@Service
@Slf4j
public class RedissonLockDemo {
@Autowired
private RedissonClient redissonClient;
/**
* 使用 Redisson 实现分布式锁
*/
public void executeWithLock(String lockKey, Runnable task) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,最多等待 10 秒,锁自动释放时间 30 秒
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
try {
log.info("获取锁成功,执行任务");
task.run();
} finally {
// 释放锁
lock.unlock();
log.info("任务执行完成,释放锁");
}
} else {
log.warn("获取锁失败,放弃执行任务");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取锁过程被中断", e);
}
}
/**
* 使用 Redisson 实现读写锁
*/
public void executeWithReadWriteLock(String lockKey, boolean isWrite, Runnable task) {
RReadWriteLock rwLock = redissonClient.getReadWriteLock(lockKey);
RLock lock = isWrite ? rwLock.writeLock() : rwLock.readLock();
try {
// 获取锁
lock.lock();
try {
log.info("获取{}锁成功,执行任务", isWrite ? "写" : "读");
task.run();
} finally {
// 释放锁
lock.unlock();
log.info("任务执行完成,释放{}锁", isWrite ? "写" : "读");
}
} catch (Exception e) {
log.error("执行任务异常", e);
}
}
}
最佳实践
1. 事务和管道
建议
- 对于需要原子性的操作,使用事务
- 对于批量操作,使用管道提高性能
- 避免在事务中执行耗时操作
2. 发布订阅
注意事项
- Redis 发布订阅不保证消息的可靠性,消息可能会丢失
- 对于需要可靠消息传递的场景,考虑使用专业的消息队列(如 RabbitMQ、Kafka)
- 发布订阅适合实时性要求高但可靠性要求不高的场景
3. Lua 脚本
建议
- 对于需要原子性执行的多个命令,使用 Lua 脚本
- 避免在 Lua 脚本中执行耗时操作,会阻塞 Redis
- 脚本应该是幂等的,以便在失败时可以安全地重试
4. 分布式锁
注意事项
- 设置合理的锁超时时间,避免死锁
- 使用唯一标识符(如 UUID)作为锁的值,确保只有获取锁的客户端才能释放锁
- 考虑使用 Redisson 等成熟的库实现分布式锁,避免自己实现的锁存在安全问题
- 对于高可用要求的场景,考虑使用 Redis 集群或哨兵模式
总结
本文详细介绍了 Redis 的高级特性:
- ✅ Redis 事务:使用 MULTI、EXEC、DISCARD 和 WATCH 命令实现事务
- ✅ Redis 管道:批量执行命令,减少网络往返时间
- ✅ 发布订阅:实现消息通信模式
- ✅ Lua 脚本:在 Redis 中执行原子操作
- ✅ 分布式锁:控制分布式系统中对共享资源的访问
下一步学习
- 学习 Redis 的集群配置和高可用方案
- 了解 Redis 的内存优化和性能调优
- 探索 Redis 在实际项目中的应用场景
希望这篇文章能帮助您深入理解 Redis 的高级特性!如果您有任何问题,欢迎在评论区讨论。