Redis相关知识
一、Redis基础概念和数据类型
1.1 Redis简介
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,可用作数据库、缓存和消息中间件。
核心特性:
- 基于内存,读写速度极快
- 支持数据持久化
- 支持多种数据结构
- 支持事务、发布订阅、主从复制等特性
1.2 五大基础数据类型
1. String(字符串)
最基本的数据类型,可以存储字符串、整数或浮点数。
命令行示例:
bash
# 设置和获取
SET name "张三"
GET name
# 设置过期时间(秒)
SETEX token 3600 "abc123"
# 原子操作
INCR counter
DECR counter
INCRBY counter 5
# 批量操作
MSET key1 "value1" key2 "value2"
MGET key1 key2
Java代码示例(Jedis):
java
import redis.clients.jedis.Jedis;
public class StringExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 设置和获取
jedis.set("username", "张三");
String username = jedis.get("username");
System.out.println("用户名: " + username);
// 设置过期时间
jedis.setex("session_id", 3600, "xyz789");
// 原子递增
jedis.set("visitor_count", "0");
Long count = jedis.incr("visitor_count");
System.out.println("访问次数: " + count);
// 批量操作
jedis.mset("name", "李四", "age", "25", "city", "北京");
List<String> values = jedis.mget("name", "age", "city");
jedis.close();
}
}
Spring Data Redis示例:
java
@Service
public class StringRedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 设置值
public void setValue(String key, String value) {
stringRedisTemplate.opsForValue().set(key, value);
}
// 设置值并设置过期时间
public void setValueWithExpire(String key, String value, long timeout) {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
// 获取值
public String getValue(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
// 原子递增
public Long increment(String key) {
return stringRedisTemplate.opsForValue().increment(key);
}
}
2. Hash(哈希)
存储对象,类似于HashMap。
命令行示例:
bash
# 设置和获取
HSET user:1001 name "王五"
HSET user:1001 age 30
HGET user:1001 name
# 批量设置
HMSET user:1002 name "赵六" age 25 city "上海"
HGETALL user:1002
# 获取所有字段
HKEYS user:1002
HVALS user:1002
# 检查字段是否存在
HEXISTS user:1002 name
# 删除字段
HDEL user:1002 city
Java代码示例:
java
public class HashExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 存储用户对象
Map<String, String> user = new HashMap<>();
user.put("id", "1001");
user.put("name", "张三");
user.put("email", "zhangsan@example.com");
user.put("age", "28");
jedis.hmset("user:1001", user);
// 获取单个字段
String name = jedis.hget("user:1001", "name");
// 获取多个字段
List<String> fields = jedis.hmget("user:1001", "name", "email");
// 获取所有字段和值
Map<String, String> userInfo = jedis.hgetAll("user:1001");
// 修改字段
jedis.hset("user:1001", "age", "29");
// 字段递增
jedis.hincrBy("user:1001", "age", 1);
jedis.close();
}
}
Spring Data Redis示例:
java
@Service
public class HashRedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 存储对象
public void saveUser(String userId, User user) {
String key = "user:" + userId;
Map<String, Object> userMap = new HashMap<>();
userMap.put("id", user.getId());
userMap.put("name", user.getName());
userMap.put("email", user.getEmail());
userMap.put("age", String.valueOf(user.getAge()));
redisTemplate.opsForHash().putAll(key, userMap);
}
// 获取对象
public User getUser(String userId) {
String key = "user:" + userId;
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
User user = new User();
user.setId((String) entries.get("id"));
user.setName((String) entries.get("name"));
user.setEmail((String) entries.get("email"));
user.setAge(Integer.parseInt((String) entries.get("age")));
return user;
}
// 更新字段
public void updateField(String userId, String field, Object value) {
String key = "user:" + userId;
redisTemplate.opsForHash().put(key, field, value);
}
}
3. List(列表)
有序的字符串列表,可以从两端插入和删除。
命令行示例:
bash
# 左侧插入
LPUSH messages "消息1"
LPUSH messages "消息2"
# 右侧插入
RPUSH messages "消息3"
# 获取列表
LRANGE messages 0 -1
# 弹出元素
LPOP messages
RPOP messages
# 获取长度
LLEN messages
# 按索引获取
LINDEX messages 0
# 修剪列表(保留指定范围)
LTRIM messages 0 99
Java代码示例:
java
public class ListExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 消息队列示例
String queueKey = "task:queue";
// 生产者:添加任务
jedis.lpush(queueKey, "任务1");
jedis.lpush(queueKey, "任务2");
jedis.lpush(queueKey, "任务3");
// 消费者:处理任务(阻塞弹出)
List<String> task = jedis.brpop(10, queueKey); // 最多等待10秒
if (task != null) {
System.out.println("处理任务: " + task.get(1));
}
// 获取最新的N条记录
String logKey = "app:logs";
jedis.lpush(logKey, "日志1");
jedis.lpush(logKey, "日志2");
jedis.lpush(logKey, "日志3");
// 只保留最新的100条
jedis.ltrim(logKey, 0, 99);
// 获取最新的10条
List<String> recentLogs = jedis.lrange(logKey, 0, 9);
jedis.close();
}
}
Spring Data Redis示例:
java
@Service
public class ListRedisService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 消息队列 - 生产者
public void pushMessage(String queue, String message) {
redisTemplate.opsForList().leftPush(queue, message);
}
// 消息队列 - 消费者
public String popMessage(String queue) {
return redisTemplate.opsForList().rightPop(queue, 10, TimeUnit.SECONDS);
}
// 添加到最近浏览列表
public void addToRecentList(String userId, String itemId) {
String key = "recent:" + userId;
// 移除已存在的
redisTemplate.opsForList().remove(key, 1, itemId);
// 添加到列表头部
redisTemplate.opsForList().leftPush(key, itemId);
// 只保留最近的10个
redisTemplate.opsForList().trim(key, 0, 9);
}
// 获取最近浏览
public List<String> getRecentList(String userId) {
String key = "recent:" + userId;
return redisTemplate.opsForList().range(key, 0, -1);
}
}
4. Set(集合)
无序的字符串集合,元素唯一。
命令行示例:
bash
# 添加元素
SADD tags "Java"
SADD tags "Redis" "Spring"
# 获取所有元素
SMEMBERS tags
# 检查元素是否存在
SISMEMBER tags "Java"
# 获取集合大小
SCARD tags
# 随机获取元素
SRANDMEMBER tags 2
# 删除元素
SREM tags "Java"
# 集合运算
SADD user:1:follows "user:2" "user:3"
SADD user:2:follows "user:1" "user:3"
# 交集(共同关注)
SINTER user:1:follows user:2:follows
# 并集(所有关注)
SUNION user:1:follows user:2:follows
# 差集(user1关注但user2未关注的)
SDIFF user:1:follows user:2:follows
Java代码示例:
java
public class SetExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 标签系统
String articleKey = "article:1001:tags";
jedis.sadd(articleKey, "Java", "Spring", "Redis");
// 获取所有标签
Set<String> tags = jedis.smembers(articleKey);
// 用户关注系统
String user1Follows = "user:1001:follows";
String user2Follows = "user:1002:follows";
jedis.sadd(user1Follows, "1002", "1003", "1004");
jedis.sadd(user2Follows, "1001", "1003", "1005");
// 共同关注
Set<String> commonFollows = jedis.sinter(user1Follows, user2Follows);
System.out.println("共同关注: " + commonFollows);
// 推荐关注(user2关注但user1未关注的)
Set<String> recommendations = jedis.sdiff(user2Follows, user1Follows);
System.out.println("推荐关注: " + recommendations);
// 抽奖系统
String lotteryKey = "lottery:participants";
jedis.sadd(lotteryKey, "user1", "user2", "user3", "user4", "user5");
// 随机抽取3个中奖者
Set<String> winners = jedis.srandmember(lotteryKey, 3);
System.out.println("中奖者: " + winners);
jedis.close();
}
}
Spring Data Redis示例:
java
@Service
public class SetRedisService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 点赞功能
public void like(String articleId, String userId) {
String key = "article:likes:" + articleId;
redisTemplate.opsForSet().add(key, userId);
}
// 取消点赞
public void unlike(String articleId, String userId) {
String key = "article:likes:" + articleId;
redisTemplate.opsForSet().remove(key, userId);
}
// 检查是否点赞
public boolean isLiked(String articleId, String userId) {
String key = "article:likes:" + articleId;
return redisTemplate.opsForSet().isMember(key, userId);
}
// 获取点赞数
public Long getLikeCount(String articleId) {
String key = "article:likes:" + articleId;
return redisTemplate.opsForSet().size(key);
}
// 获取共同好友
public Set<String> getCommonFriends(String userId1, String userId2) {
String key1 = "user:friends:" + userId1;
String key2 = "user:friends:" + userId2;
return redisTemplate.opsForSet().intersect(key1, key2);
}
}
5. ZSet(有序集合)
有序的字符串集合,每个元素关联一个分数。
命令行示例:
bash
# 添加元素
ZADD ranking 100 "张三"
ZADD ranking 85 "李四"
ZADD ranking 95 "王五"
# 获取排名(从高到低)
ZREVRANGE ranking 0 -1 WITHSCORES
# 获取排名(从低到高)
ZRANGE ranking 0 -1 WITHSCORES
# 获取分数
ZSCORE ranking "张三"
# 获取排名
ZREVRANK ranking "张三" # 从高到低的排名
ZRANK ranking "张三" # 从低到高的排名
# 增加分数
ZINCRBY ranking 10 "李四"
# 获取指定分数范围
ZRANGEBYSCORE ranking 90 100
# 删除元素
ZREM ranking "王五"
Java代码示例:
java
public class ZSetExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 游戏排行榜
String leaderboardKey = "game:leaderboard";
// 添加玩家分数
jedis.zadd(leaderboardKey, 2500, "player1");
jedis.zadd(leaderboardKey, 3200, "player2");
jedis.zadd(leaderboardKey, 2800, "player3");
jedis.zadd(leaderboardKey, 3500, "player4");
// 获取TOP 3
Set<Tuple> top3 = jedis.zrevrangeWithScores(leaderboardKey, 0, 2);
System.out.println("TOP 3 玩家:");
for (Tuple player : top3) {
System.out.println(player.getElement() + ": " + player.getScore());
}
// 获取玩家排名(0-based)
Long rank = jedis.zrevrank(leaderboardKey, "player3");
System.out.println("player3 排名: " + (rank + 1));
// 增加分数
jedis.zincrby(leaderboardKey, 500, "player1");
// 热门文章(按浏览量排序)
String hotArticlesKey = "articles:hot";
jedis.zadd(hotArticlesKey, 1000, "article:1");
jedis.zadd(hotArticlesKey, 1500, "article:2");
jedis.zadd(hotArticlesKey, 800, "article:3");
// 文章被浏览
jedis.zincrby(hotArticlesKey, 1, "article:1");
// 获取热门文章TOP 10
Set<String> hotArticles = jedis.zrevrange(hotArticlesKey, 0, 9);
jedis.close();
}
}
Spring Data Redis示例:
java
@Service
public class ZSetRedisService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 更新排行榜分数
public void updateScore(String leaderboard, String player, double score) {
redisTemplate.opsForZSet().add(leaderboard, player, score);
}
// 增加分数
public void incrementScore(String leaderboard, String player, double delta) {
redisTemplate.opsForZSet().incrementScore(leaderboard, player, delta);
}
// 获取排行榜TOP N
public Set<ZSetOperations.TypedTuple<String>> getTopN(String leaderboard, int n) {
return redisTemplate.opsForZSet().reverseRangeWithScores(leaderboard, 0, n - 1);
}
// 获取玩家排名
public Long getRank(String leaderboard, String player) {
Long rank = redisTemplate.opsForZSet().reverseRank(leaderboard, player);
return rank != null ? rank + 1 : null;
}
// 延迟队列实现
public void addDelayedTask(String queue, String task, long delaySeconds) {
long score = System.currentTimeMillis() + (delaySeconds * 1000);
redisTemplate.opsForZSet().add(queue, task, score);
}
// 获取到期的任务
public Set<String> getExpiredTasks(String queue) {
long now = System.currentTimeMillis();
Set<String> tasks = redisTemplate.opsForZSet().rangeByScore(queue, 0, now);
// 删除已获取的任务
if (!tasks.isEmpty()) {
redisTemplate.opsForZSet().removeRangeByScore(queue, 0, now);
}
return tasks;
}
}
二、Redis持久化机制
Redis提供两种持久化方式:RDB和AOF。
2.1 RDB(Redis Database)
定时将内存中的数据快照保存到磁盘。
配置示例(redis.conf):
bash
# 保存策略
save 900 1 # 900秒内至少1个key变化则保存
save 300 10 # 300秒内至少10个key变化则保存
save 60 10000 # 60秒内至少10000个key变化则保存
# RDB文件名
dbfilename dump.rdb
# RDB文件目录
dir /var/lib/redis/
# 压缩
rdbcompression yes
# 校验
rdbchecksum yes
手动触发RDB:
bash
# 阻塞保存
SAVE
# 后台保存(推荐)
BGSAVE
# 获取最后保存时间
LASTSAVE
Java代码示例:
java
public class RDBExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 手动触发后台保存
String result = jedis.bgsave();
System.out.println("BGSAVE结果: " + result);
// 获取最后保存时间
Long lastSaveTime = jedis.lastsave();
Date lastSave = new Date(lastSaveTime * 1000);
System.out.println("最后保存时间: " + lastSave);
// 通过CONFIG命令查看和修改配置
List<String> saveConfig = jedis.configGet("save");
System.out.println("当前save配置: " + saveConfig);
jedis.close();
}
}
2.2 AOF(Append Only File)
将每个写操作记录到日志文件。
配置示例(redis.conf):
bash
# 开启AOF
appendonly yes
# AOF文件名
appendfilename "appendonly.aof"
# 同步策略
appendfsync everysec # 每秒同步(推荐)
# appendfsync always # 每次写操作都同步
# appendfsync no # 由操作系统决定
# AOF重写
auto-aof-rewrite-percentage 100 # 文件增长100%时重写
auto-aof-rewrite-min-size 64mb # 文件最小64MB才重写
AOF管理命令:
bash
# 手动重写AOF
BGREWRITEAOF
# 查看AOF状态
INFO persistence
Java代码配置示例:
java
@Configuration
public class RedisPersistenceConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("localhost");
config.setPort(6379);
// 配置连接池
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(10);
poolConfig.setMaxIdle(5);
poolConfig.setMinIdle(1);
LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration
.builder()
.poolConfig(poolConfig)
.build();
return new LettuceConnectionFactory(config, clientConfig);
}
// 定时触发RDB保存
@Component
public class RedisPersistenceTask {
@Autowired
private StringRedisTemplate redisTemplate;
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void triggerBgSave() {
redisTemplate.execute((RedisCallback<String>) connection -> {
connection.bgSave();
return "OK";
});
}
}
}
三、Redis事务和管道
3.1 Redis事务
Redis事务通过MULTI、EXEC、DISCARD和WATCH命令实现。
命令行示例:
bash
# 开启事务
MULTI
# 事务中的命令(入队)
SET key1 "value1"
SET key2 "value2"
INCR counter
# 执行事务
EXEC
# 取消事务
MULTI
SET key3 "value3"
DISCARD # 取消事务
# 乐观锁(WATCH)
WATCH balance
MULTI
DECRBY balance 100
INCRBY spending 100
EXEC # 如果balance在事务期间被修改,事务会失败
Java事务示例:
java
public class TransactionExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 转账示例
String account1 = "account:1001";
String account2 = "account:1002";
// 初始化账户
jedis.set(account1, "1000");
jedis.set(account2, "500");
// 执行转账事务
Transaction tx = jedis.multi();
try {
tx.decrBy(account1, 200); // 账户1减200
tx.incrBy(account2, 200); // 账户2加200
List<Object> results = tx.exec();
if (results != null) {
System.out.println("转账成功");
} else {
System.out.println("转账失败");
}
} catch (Exception e) {
tx.discard();
System.out.println("事务异常: " + e.getMessage());
}
// 使用WATCH实现乐观锁
boolean success = false;
while (!success) {
jedis.watch(account1);
int balance = Integer.parseInt(jedis.get(account1));
if (balance >= 100) {
Transaction transaction = jedis.multi();
transaction.decrBy(account1, 100);
List<Object> result = transaction.exec();
if (result != null) {
success = true;
System.out.println("扣款成功");
} else {
System.out.println("账户被其他事务修改,重试...");
}
} else {
jedis.unwatch();
System.out.println("余额不足");
break;
}
}
jedis.close();
}
}
Spring Data Redis事务示例:
java
@Service
public class TransactionService {
@Autowired
private StringRedisTemplate redisTemplate;
// 简单事务
public void simpleTransaction() {
redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) {
operations.multi();
operations.opsForValue().set("key1", "value1");
operations.opsForValue().set("key2", "value2");
operations.opsForValue().increment("counter");
return operations.exec();
}
});
}
// 转账事务(带乐观锁)
public boolean transfer(String fromAccount, String toAccount, int amount) {
return redisTemplate.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(RedisOperations operations) {
operations.watch(fromAccount);
String balanceStr = (String) operations.opsForValue().get(fromAccount);
int balance = Integer.parseInt(balanceStr);
if (balance < amount) {
operations.unwatch();
return false;
}
operations.multi();
operations.opsForValue().decrement(fromAccount, amount);
operations.opsForValue().increment(toAccount, amount);
List<Object> results = operations.exec();
return results != null && !results.isEmpty();
}
});
}
}
3.2 Pipeline(管道)
批量执行命令,减少网络往返时间。
Java Pipeline示例:
java
public class PipelineExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
// 不使用Pipeline(慢)
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
jedis.set("key" + i, "value" + i);
}
System.out.println("普通方式耗时: " + (System.currentTimeMillis() - start) + "ms");
// 使用Pipeline(快)
start = System.currentTimeMillis();
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 10000; i++) {
pipeline.set("pkey" + i, "value" + i);
}
pipeline.sync(); // 执行所有命令
System.out.println("Pipeline方式耗时: " + (System.currentTimeMillis() - start) + "ms");
// Pipeline获取响应
Pipeline p = jedis.pipelined();
Response<String> r1 = p.get("key1");
Response<String> r2 = p.get("key2");
Response<Long> r3 = p.incr("counter");
p.sync();
System.out.println("key1: " + r1.get());
System.out.println("key2: " + r2.get());
System.out.println("counter: " + r3.get());
jedis.close();
}
}
Spring Data Redis Pipeline示例:
java
@Service
public class PipelineService {
@Autowired
private StringRedisTemplate redisTemplate;
// 批量设置
public void batchSet(Map<String, String> data) {
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) {
for (Map.Entry<String, String> entry : data.entrySet()) {
operations.opsForValue().set(entry.getKey(), entry.getValue());
}
return null;
}
});
}
// 批量获取
public List<Object> batchGet(List<String> keys) {
return redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) {
for (String key : keys) {
operations.opsForValue().get(key);
}
return null;
}
});
}
// 批量操作不同数据类型
public void mixedOperations() {
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
StringRedisConnection stringConnection = (StringRedisConnection) connection;
// String操作
stringConnection.set("string:key", "value");
// Hash操作
stringConnection.hSet("hash:key", "field", "value");
// List操作
stringConnection.lPush("list:key", "item1", "item2");
// Set操作
stringConnection.sAdd("set:key", "member1", "member2");
// ZSet操作
stringConnection.zAdd("zset:key", 100, "member1");
return null;
});
}
}
四、Redis发布订阅
Redis提供了发布订阅(Pub/Sub)消息模式。
4.1 基本命令
命令行示例:
bash
# 订阅者终端1
SUBSCRIBE news sports
# 订阅者终端2(模式订阅)
PSUBSCRIBE news:*
# 发布者终端
PUBLISH news "重要新闻"
PUBLISH news:tech "科技新闻"
PUBLISH sports "体育新闻"
# 查看活跃频道
PUBSUB CHANNELS
# 查看频道订阅者数量
PUBSUB NUMSUB news sports
4.2 Java实现
Jedis发布订阅示例:
java
// 订阅者
public class Subscriber extends JedisPubSub {
@Override
public void onMessage(String channel, String message) {
System.out.println("收到消息 - 频道: " + channel + ", 内容: " + message);
}
@Override
public void onPMessage(String pattern, String channel, String message) {
System.out.println("模式订阅 - 模式: " + pattern + ", 频道: " + channel + ", 内容: " + message);
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
System.out.println("订阅频道: " + channel);
}
}
// 订阅者线程
public class SubscriberThread extends Thread {
private Jedis jedis;
private Subscriber subscriber;
private String[] channels;
public SubscriberThread(String[] channels) {
this.jedis = new Jedis("localhost", 6379);
this.subscriber = new Subscriber();
this.channels = channels;
}
@Override
public void run() {
jedis.subscribe(subscriber, channels);
}
public void unsubscribe() {
subscriber.unsubscribe();
}
}
// 发布者
public class Publisher {
public static void main(String[] args) throws InterruptedException {
Jedis jedis = new Jedis("localhost", 6379);
// 启动订阅者
SubscriberThread sub1 = new SubscriberThread(new String[]{"news", "sports"});
sub1.start();
Thread.sleep(1000); // 等待订阅者就绪
// 发布消息
jedis.publish("news", "今日头条新闻");
jedis.publish("sports", "体育赛事直播");
jedis.publish("news", "突发新闻");
// 查看订阅者数量
Map<String, String> result = jedis.pubsubNumSub("news", "sports");
System.out.println("订阅者数量: " + result);
jedis.close();
}
}
Spring Data Redis发布订阅示例:
java
@Configuration
public class RedisPubSubConfig {
@Bean
public RedisMessageListenerContainer messageListenerContainer(
RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 添加消息监听器
container.addMessageListener(newsMessageListener(), newsTopic());
container.addMessageListener(sportsMessageListener(), sportsTopic());
// 模式订阅
container.addMessageListener(patternMessageListener(),
new PatternTopic("news:*"));
return container;
}
@Bean
public MessageListener newsMessageListener() {
return (message, pattern) -> {
String channel = new String(message.getChannel());
String body = new String(message.getBody());
System.out.println("新闻频道消息 - " + channel + ": " + body);
};
}
@Bean
public MessageListener sportsMessageListener() {
return (message, pattern) -> {
String channel = new String(message.getChannel());
String body = new String(message.getBody());
System.out.println("体育频道消息 - " + channel + ": " + body);
};
}
@Bean
public MessageListener patternMessageListener() {
return (message, pattern) -> {
String channel = new String(message.getChannel());
String body = new String(message.getBody());
System.out.println("模式匹配消息 - " + channel + ": " + body);
};
}
@Bean
public ChannelTopic newsTopic() {
return new ChannelTopic("news");
}
@Bean
public ChannelTopic sportsTopic() {
return new ChannelTopic("sports");
}
}
// 发布服务
@Service
public class PublishService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 发布消息
public void publishMessage(String channel, String message) {
redisTemplate.convertAndSend(channel, message);
}
// 发布对象(自动序列化)
public void publishObject(String channel, Object obj) {
redisTemplate.convertAndSend(channel, obj);
}
}
// 实际应用示例:实时通知
@Service
public class NotificationService {
@Autowired
private PublishService publishService;
// 发送系统通知
public void sendSystemNotification(String message) {
publishService.publishMessage("system:notification", message);
}
// 发送用户消息
public void sendUserMessage(String userId, String message) {
publishService.publishMessage("user:" + userId, message);
}
// 发送订单状态更新
public void sendOrderUpdate(String orderId, String status) {
Map<String, String> update = new HashMap<>();
update.put("orderId", orderId);
update.put("status", status);
update.put("timestamp", String.valueOf(System.currentTimeMillis()));
publishService.publishObject("order:updates", update);
}
}
五、Redis集群和主从复制
5.1 主从复制
主从复制实现读写分离,提高性能和可用性。
配置从节点(redis.conf):
bash
# 从节点配置
replicaof 127.0.0.1 6379 # 指定主节点
masterauth password123 # 主节点密码(如果有)
# 从节点只读
replica-read-only yes
# 复制积压缓冲区大小
repl-backlog-size 64mb
命令行操作:
bash
# 动态设置从节点
REPLICAOF 127.0.0.1 6379
# 查看复制信息
INFO replication
# 取消复制(变为主节点)
REPLICAOF NO ONE
Java代码配置主从:
java
// 读写分离配置
@Configuration
public class RedisReplicationConfig {
// 主节点(写操作)
@Bean
@Primary
public LettuceConnectionFactory masterConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("127.0.0.1");
config.setPort(6379);
config.setPassword("password123");
return new LettuceConnectionFactory(config);
}
// 从节点(读操作)
@Bean
public LettuceConnectionFactory slaveConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("127.0.0.1");
config.setPort(6380); // 从节点端口
config.setPassword("password123");
return new LettuceConnectionFactory(config);
}
// 写操作模板
@Bean
public RedisTemplate<String, Object> masterRedisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(masterConnectionFactory());
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
// 读操作模板
@Bean
public RedisTemplate<String, Object> slaveRedisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(slaveConnectionFactory());
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
// 读写分离服务
@Service
public class ReadWriteSplitService {
@Autowired
@Qualifier("masterRedisTemplate")
private RedisTemplate<String, Object> masterTemplate;
@Autowired
@Qualifier("slaveRedisTemplate")
private RedisTemplate<String, Object> slaveTemplate;
// 写操作使用主节点
public void write(String key, Object value) {
masterTemplate.opsForValue().set(key, value);
}
// 读操作使用从节点
public Object read(String key) {
return slaveTemplate.opsForValue().get(key);
}
}
5.2 哨兵模式(Sentinel)
哨兵提供自动故障转移和监控。
哨兵配置(sentinel.conf):
bash
# 端口
port 26379
# 监控主节点
sentinel monitor mymaster 127.0.0.1 6379 2 # 2个哨兵同意才故障转移
# 主节点密码
sentinel auth-pass mymaster password123
# 故障转移超时
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
Spring Boot配置哨兵:
java
@Configuration
public class RedisSentinelConfig {
@Bean
public LettuceConnectionFactory sentinelConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("127.0.0.1", 26379)
.sentinel("127.0.0.1", 26380)
.sentinel("127.0.0.1", 26381);
sentinelConfig.setPassword("password123");
return new LettuceConnectionFactory(sentinelConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(sentinelConnectionFactory());
// 配置序列化
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setDefaultSerializer(serializer);
return template;
}
}
5.3 Redis Cluster(集群)
Redis Cluster提供数据分片和高可用性。
集群配置(redis.conf):
bash
# 开启集群模式
cluster-enabled yes
# 集群配置文件
cluster-config-file nodes-6379.conf
# 集群节点超时
cluster-node-timeout 15000
# 集群需要的最小副本数
cluster-migration-barrier 1
创建集群命令:
bash
# 创建集群(3主3从)
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
Spring Boot配置集群:
java
@Configuration
public class RedisClusterConfig {
@Bean
public LettuceConnectionFactory clusterConnectionFactory() {
// 集群节点
List<String> nodes = Arrays.asList(
"127.0.0.1:7000",
"127.0.0.1:7001",
"127.0.0.1:7002",
"127.0.0.1:7003",
"127.0.0.1:7004",
"127.0.0.1:7005"
);
RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(nodes);
clusterConfig.setPassword("password123");
// 配置连接池
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(50);
poolConfig.setMinIdle(10);
LettucePoolingClientConfiguration clientConfig =
LettucePoolingClientConfiguration.builder()
.poolConfig(poolConfig)
.commandTimeout(Duration.ofSeconds(2))
.build();
return new LettuceConnectionFactory(clusterConfig, clientConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(clusterConnectionFactory());
// 配置序列化
StringRedisSerializer stringSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jsonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
return template;
}
}
// 集群操作服务
@Service
public class RedisClusterService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 批量操作(会自动路由到不同节点)
public void batchSet(Map<String, Object> data) {
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (Map.Entry<String, Object> entry : data.entrySet()) {
byte[] key = entry.getKey().getBytes();
byte[] value = SerializationUtils.serialize((Serializable) entry.getValue());
connection.set(key, value);
}
return null;
});
}
// 获取集群信息
public Properties getClusterInfo() {
return redisTemplate.getConnectionFactory()
.getClusterConnection()
.clusterGetClusterInfo();
}
// 获取槽位分配
public Map<String, List<Integer>> getSlotDistribution() {
RedisClusterConnection connection = redisTemplate
.getConnectionFactory()
.getClusterConnection();
Map<String, List<Integer>> distribution = new HashMap<>();
for (RedisClusterNode node : connection.clusterGetNodes()) {
if (node.isMaster()) {
String nodeId = node.getId();
List<Integer> slots = new ArrayList<>();
for (RedisClusterNode.SlotRange range : node.getSlotRanges()) {
for (int i = range.getFrom(); i <= range.getTo(); i++) {
slots.add(i);
}
}
distribution.put(nodeId, slots);
}
}
return distribution;
}
}
六、Spring Boot集成Redis
6.1 依赖配置
pom.xml:
xml
<dependencies>
<!-- Spring Boot Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lettuce连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Jackson序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
6.2 配置文件
application.yml:
yaml
spring:
redis:
# 单机配置
host: localhost
port: 6379
password: password123
database: 0
# 连接池配置
lettuce:
pool:
max-active: 100
max-idle: 50
min-idle: 10
max-wait: 3000ms
# 超时配置
timeout: 5000ms
connect-timeout: 3000ms
# 缓存配置
cache:
type: redis
redis:
time-to-live: 3600000 # 1小时
cache-null-values: false
use-key-prefix: true
key-prefix: "cache:"
6.3 完整配置类
java
@Configuration
@EnableCaching // 开启缓存
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Jackson序列化配置
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key使用String序列化
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
// value使用Jackson序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
return template;
}
// 缓存管理器
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 默认过期时间
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues(); // 不缓存null值
// 设置不同缓存的过期时间
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put("users", config.entryTtl(Duration.ofMinutes(30)));
cacheConfigurations.put("products", config.entryTtl(Duration.ofHours(2)));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
// Redis连接工厂配置
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// 连接池配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(50);
poolConfig.setMinIdle(10);
poolConfig.setMaxWaitMillis(3000);
// 客户端配置
LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration
.builder()
.poolConfig(poolConfig)
.commandTimeout(Duration.ofSeconds(2))
.shutdownTimeout(Duration.ofMillis(100))
.build();
// Redis配置
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName("localhost");
redisConfig.setPort(6379);
redisConfig.setPassword("password123");
redisConfig.setDatabase(0);
return new LettuceConnectionFactory(redisConfig, clientConfig);
}
}
6.4 缓存注解使用
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 缓存查询结果
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
System.out.println("从数据库查询用户: " + id);
return userRepository.findById(id).orElse(null);
}
// 更新时清除缓存
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
// 删除缓存
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// 清除所有缓存
@CacheEvict(value = "users", allEntries = true)
public void clearAllUserCache() {
System.out.println("清除所有用户缓存");
}
// 条件缓存
@Cacheable(value = "users", key = "#name", condition = "#name.length() > 3")
public List<User> getUsersByName(String name) {
return userRepository.findByName(name);
}
// 多个缓存操作
@Caching(
cacheable = @Cacheable(value = "users", key = "#email"),
put = @CachePut(value = "userEmails", key = "#result.id"),
evict = @CacheEvict(value = "oldUsers", allEntries = true)
)
public User getUserByEmail(String email) {
return userRepository.findByEmail(email);
}
}
6.5 Redis工具类
java
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// ==================== Common ====================
public boolean expire(String key, long time) {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
return true;
}
return false;
}
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
public void del(String... keys) {
if (keys != null && keys.length > 0) {
redisTemplate.delete(Arrays.asList(keys));
}
}
// ==================== String ====================
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
public boolean set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
return true;
}
public boolean set(String key, Object value, long time) {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
}
public long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
public long decr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, -delta);
}
// ==================== Hash ====================
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
public boolean hmset(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
return true;
}
public boolean hset(String key, String item, Object value) {
redisTemplate.opsForHash().put(key, item, value);
return true;
}
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
// ==================== List ====================
public List<Object> lGet(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
public long lGetListSize(String key) {
return redisTemplate.opsForList().size(key);
}
public Object lGetIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
public boolean lSet(String key, Object value) {
redisTemplate.opsForList().rightPush(key, value);
return true;
}
public boolean lSet(String key, List<Object> value) {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
}
// ==================== Set ====================
public Set<Object> sGet(String key) {
return redisTemplate.opsForSet().members(key);
}
public boolean sHasKey(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
public long sSet(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
public long sGetSetSize(String key) {
return redisTemplate.opsForSet().size(key);
}
public long setRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
// ==================== ZSet ====================
public boolean zAdd(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
public Set<Object> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
public Set<Object> zReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
public Long zRank(String key, Object value) {
return redisTemplate.opsForZSet().rank(key, value);
}
public Double zScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
// ==================== 分布式锁 ====================
public boolean lock(String key, String value, long expireTime) {
return redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
}
public boolean unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript,
Collections.singletonList(key), value);
return result != null && result == 1;
}
}
七、Redis性能优化和实际应用场景
7.1 性能优化策略
1. 内存优化
java
@Component
public class RedisMemoryOptimizer {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 设置key过期时间,避免内存泄漏
public void setWithExpire(String key, Object value, long seconds) {
redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
}
// 批量删除过期key
@Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点执行
public void cleanExpiredKeys() {
Set<String> keys = redisTemplate.keys("temp:*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
// 使用位图节省内存(用户签到示例)
public void userSignIn(long userId, int dayOfMonth) {
String key = "sign:" + userId + ":" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
redisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
}
// 获取用户月签到情况
public long getMonthSignCount(long userId) {
String key = "sign:" + userId + ":" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
return redisTemplate.execute((RedisCallback<Long>) connection -> {
byte[] keyBytes = key.getBytes();
return connection.bitCount(keyBytes);
});
}
}
2. 命令优化
java
@Service
public class RedisCommandOptimizer {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 避免使用KEYS命令,使用SCAN代替
public Set<String> scanKeys(String pattern) {
return redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Set<String> keys = new HashSet<>();
Cursor<byte[]> cursor = connection.scan(
ScanOptions.scanOptions()
.match(pattern)
.count(1000)
.build()
);
while (cursor.hasNext()) {
keys.add(new String(cursor.next()));
}
return keys;
});
}
// 使用Pipeline批量操作
public List<Object> batchGet(List<String> keys) {
return redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
StringRedisConnection stringConnection = (StringRedisConnection) connection;
for (String key : keys) {
stringConnection.get(key);
}
return null;
});
}
// 避免大key,分片存储
public void saveLargeData(String key, List<String> largeList, int chunkSize) {
int chunks = (largeList.size() + chunkSize - 1) / chunkSize;
for (int i = 0; i < chunks; i++) {
int start = i * chunkSize;
int end = Math.min(start + chunkSize, largeList.size());
List<String> chunk = largeList.subList(start, end);
String chunkKey = key + ":chunk:" + i;
redisTemplate.opsForList().rightPushAll(chunkKey, chunk);
redisTemplate.expire(chunkKey, 1, TimeUnit.HOURS);
}
// 保存分片信息
redisTemplate.opsForValue().set(key + ":chunks", String.valueOf(chunks));
}
}
7.2 实际应用场景
1. 分布式锁
java
@Component
public class RedisDistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
// 获取锁
public boolean tryLock(String lockKey, String requestId, long expireTime) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
return result != null && result;
}
// 释放锁(使用Lua脚本保证原子性)
public boolean releaseLock(String lockKey, String requestId) {
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript,
Collections.singletonList(lockKey), requestId);
return result != null && result == 1;
}
// 使用示例
public void processWithLock(String businessKey) {
String lockKey = "lock:" + businessKey;
String requestId = UUID.randomUUID().toString();
try {
// 尝试获取锁,最多等待5秒
boolean locked = false;
for (int i = 0; i < 10; i++) {
if (tryLock(lockKey, requestId, 30)) {
locked = true;
break;
}
Thread.sleep(500);
}
if (locked) {
// 执行业务逻辑
System.out.println("获得锁,执行业务逻辑");
Thread.sleep(1000);
} else {
System.out.println("获取锁失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
releaseLock(lockKey, requestId);
}
}
}
2. 限流器
java
@Component
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
// 简单计数器限流
public boolean isAllowed(String key, int limit, int window) {
Long current = redisTemplate.opsForValue().increment(key);
if (current == 1) {
redisTemplate.expire(key, window, TimeUnit.SECONDS);
}
return current <= limit;
}
// 滑动窗口限流
public boolean isAllowedSlidingWindow(String key, int limit, int window) {
long now = System.currentTimeMillis();
long windowStart = now - window * 1000;
// 使用zset实现滑动窗口
String requestId = UUID.randomUUID().toString();
// 添加当前请求
redisTemplate.opsForZSet().add(key, requestId, now);
// 移除窗口外的请求
redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
// 统计窗口内请求数
Long count = redisTemplate.opsForZSet().count(key, windowStart, now);
// 设置过期时间
redisTemplate.expire(key, window + 1, TimeUnit.SECONDS);
return count <= limit;
}
// 令牌桶限流(使用Lua脚本)
public boolean isAllowedTokenBucket(String key, int capacity, int rate) {
String script =
"local key = KEYS[1]\n" +
"local capacity = tonumber(ARGV[1])\n" +
"local rate = tonumber(ARGV[2])\n" +
"local now = tonumber(ARGV[3])\n" +
"local requested = tonumber(ARGV[4])\n" +
"\n" +
"local bucket = redis.call('hgetall', key)\n" +
"local tokens = capacity\n" +
"local last_refill = now\n" +
"\n" +
"if #bucket > 0 then\n" +
" tokens = tonumber(bucket[2])\n" +
" last_refill = tonumber(bucket[4])\n" +
"end\n" +
"\n" +
"local elapsed = math.max(0, now - last_refill)\n" +
"local filled_tokens = math.min(capacity, tokens + elapsed * rate / 1000)\n" +
"\n" +
"if filled_tokens >= requested then\n" +
" redis.call('hset', key, 'tokens', filled_tokens - requested, 'last_refill', now)\n" +
" redis.call('expire', key, 10)\n" +
" return 1\n" +
"else\n" +
" redis.call('hset', key, 'tokens', filled_tokens, 'last_refill', now)\n" +
" redis.call('expire', key, 10)\n" +
" return 0\n" +
"end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript,
Collections.singletonList(key),
String.valueOf(capacity),
String.valueOf(rate),
String.valueOf(System.currentTimeMillis()),
"1");
return result != null && result == 1;
}
}
3. 会话管理
java
@Service
public class SessionManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final long SESSION_TIMEOUT = 30 * 60; // 30分钟
// 创建会话
public String createSession(Long userId, Map<String, Object> userInfo) {
String sessionId = UUID.randomUUID().toString();
String sessionKey = "session:" + sessionId;
Map<String, Object> sessionData = new HashMap<>();
sessionData.put("userId", userId);
sessionData.put("createTime", System.currentTimeMillis());
sessionData.putAll(userInfo);
redisTemplate.opsForHash().putAll(sessionKey, sessionData);
redisTemplate.expire(sessionKey, SESSION_TIMEOUT, TimeUnit.SECONDS);
// 维护用户会话索引
String userSessionKey = "user:sessions:" + userId;
redisTemplate.opsForSet().add(userSessionKey, sessionId);
redisTemplate.expire(userSessionKey, SESSION_TIMEOUT, TimeUnit.SECONDS);
return sessionId;
}
// 获取会话
public Map<Object, Object> getSession(String sessionId) {
String sessionKey = "session:" + sessionId;
Map<Object, Object> session = redisTemplate.opsForHash().entries(sessionKey);
if (!session.isEmpty()) {
// 刷新过期时间
redisTemplate.expire(sessionKey, SESSION_TIMEOUT, TimeUnit.SECONDS);
}
return session;
}
// 销毁会话
public void destroySession(String sessionId) {
String sessionKey = "session:" + sessionId;
Map<Object, Object> session = redisTemplate.opsForHash().entries(sessionKey);
if (session.containsKey("userId")) {
Long userId = Long.valueOf(session.get("userId").toString());
String userSessionKey = "user:sessions:" + userId;
redisTemplate.opsForSet().remove(userSessionKey, sessionId);
}
redisTemplate.delete(sessionKey);
}
// 获取用户所有会话
public Set<Object> getUserSessions(Long userId) {
String userSessionKey = "user:sessions:" + userId;
return redisTemplate.opsForSet().members(userSessionKey);
}
}
4. 延迟队列
java
@Component
public class DelayQueue {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String DELAY_QUEUE_KEY = "delay:queue";
// 添加延迟任务
public void addDelayTask(String taskId, String taskData, long delaySeconds) {
long score = System.currentTimeMillis() + (delaySeconds * 1000);
Map<String, String> task = new HashMap<>();
task.put("id", taskId);
task.put("data", taskData);
String taskJson = JSON.toJSONString(task);
redisTemplate.opsForZSet().add(DELAY_QUEUE_KEY, taskJson, score);
}
// 消费延迟任务
@Scheduled(fixedDelay = 1000) // 每秒检查一次
public void consumeDelayTasks() {
long now = System.currentTimeMillis();
Set<String> tasks = redisTemplate.opsForZSet()
.rangeByScore(DELAY_QUEUE_KEY, 0, now);
if (tasks != null && !tasks.isEmpty()) {
for (String taskJson : tasks) {
// 移除任务
Long removed = redisTemplate.opsForZSet().remove(DELAY_QUEUE_KEY, taskJson);
if (removed != null && removed > 0) {
// 处理任务
Map<String, String> task = JSON.parseObject(taskJson, Map.class);
processTask(task.get("id"), task.get("data"));
}
}
}
}
private void processTask(String taskId, String taskData) {
System.out.println("处理延迟任务 - ID: " + taskId + ", 数据: " + taskData);
// 实际业务处理逻辑
}
}
5. 实时排行榜
java
@Service
public class LeaderboardService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 更新分数
public void updateScore(String leaderboard, String player, double score) {
redisTemplate.opsForZSet().add(leaderboard, player, score);
}
// 增加分数
public void incrementScore(String leaderboard, String player, double increment) {
redisTemplate.opsForZSet().incrementScore(leaderboard, player, increment);
}
// 获取排行榜
public List<Map<String, Object>> getLeaderboard(String leaderboard, int topN) {
Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(leaderboard, 0, topN - 1);
List<Map<String, Object>> result = new ArrayList<>();
int rank = 1;
for (ZSetOperations.TypedTuple<String> tuple : tuples) {
Map<String, Object> entry = new HashMap<>();
entry.put("rank", rank++);
entry.put("player", tuple.getValue());
entry.put("score", tuple.getScore());
result.add(entry);
}
return result;
}
// 获取玩家排名和分数
public Map<String, Object> getPlayerRankAndScore(String leaderboard, String player) {
Map<String, Object> result = new HashMap<>();
Long rank = redisTemplate.opsForZSet().reverseRank(leaderboard, player);
Double score = redisTemplate.opsForZSet().score(leaderboard, player);
result.put("player", player);
result.put("rank", rank != null ? rank + 1 : null);
result.put("score", score);
return result;
}
// 获取排名区间的玩家
public Set<String> getPlayersByRankRange(String leaderboard, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(leaderboard, start - 1, end - 1);
}
// 获取分数区间的玩家
public Set<String> getPlayersByScoreRange(String leaderboard, double minScore, double maxScore) {
return redisTemplate.opsForZSet().rangeByScore(leaderboard, minScore, maxScore);
}
}