Redis 实战指南:高性能缓存与数据结构实践
目录
- [1. Redis 简介](#1. Redis 简介 "#1-redis-%E7%AE%80%E4%BB%8B")
- [2. Redis 数据结构](#2. Redis 数据结构 "#2-redis-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84")
- [3. Java 客户端集成](#3. Java 客户端集成 "#3-java-%E5%AE%A2%E6%88%B7%E7%AB%AF%E9%9B%86%E6%88%90")
- [4. 缓存实战场景](#4. 缓存实战场景 "#4-%E7%BC%93%E5%AD%98%E5%AE%9E%E6%88%98%E5%9C%BA%E6%99%AF")
- [5. 分布式锁实现](#5. 分布式锁实现 "#5-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0")
- [6. 消息队列与发布订阅](#6. 消息队列与发布订阅 "#6-%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E4%B8%8E%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85")
- [7. 持久化机制](#7. 持久化机制 "#7-%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6")
- [8. 集群架构](#8. 集群架构 "#8-%E9%9B%86%E7%BE%A4%E6%9E%B6%E6%9E%84")
- [9. 性能优化](#9. 性能优化 "#9-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96")
- [10. 常见问题与解决方案](#10. 常见问题与解决方案 "#10-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88")
- [11. Spring Boot 集成](#11. Spring Boot 集成 "#11-spring-boot-%E9%9B%86%E6%88%90")
- [12. 监控与运维](#12. 监控与运维 "#12-%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%BF%90%E7%BB%B4")
- [13. 总结](#13. 总结 "#13-%E6%80%BB%E7%BB%93")
1. Redis 简介
Redis (Remote Dictionary Server) 是一个开源的高性能键值对数据库,支持多种数据结构,广泛用于缓存、消息队列、分布式锁等场景。
1.1 核心特性
- 高性能: 单机10万+ QPS,基于内存操作
- 丰富数据结构: String、Hash、List、Set、ZSet等
- 持久化: 支持RDB和AOF两种持久化方式
- 高可用: 支持主从复制、哨兵、集群模式
- 原子操作: 所有操作都是原子性的
- Lua脚本: 支持服务端Lua脚本
- 事务支持: MULTI/EXEC事务机制
1.2 应用场景
markdown
Redis 典型应用场景
1. 缓存系统
┌────────┐ Miss ┌────────┐
│ Client │────────────▶│ Redis │
└────────┘ └────────┘
│ │
│ Hit │ Miss
▼ ▼
┌────────┐ ┌────────┐
│ Result │◀────────────│ DB │
└────────┘ Set Cache └────────┘
2. 分布式锁
Thread A ──▶ SETNX lock ──▶ 获得锁 ──▶ 执行业务
Thread B ──▶ SETNX lock ──▶ 等待
3. 计数器/限流
INCR user:1001:requests
4. 排行榜
ZADD leaderboard score user
5. 消息队列
LPUSH / BRPOP
6. 会话存储
Session ID ──▶ Redis ──▶ User Data
1.3 Redis vs 其他缓存
性能与特性对比
┌─────────────────┬──────────┬──────────┬──────────┐
│ Feature │ Redis │ Memcached│ Ehcache │
├─────────────────┼──────────┼──────────┼──────────┤
│ 数据结构 │ 丰富 │ 简单 │ 简单 │
├─────────────────┼──────────┼──────────┼──────────┤
│ 持久化 │ 支持 │ 不支持 │ 支持 │
├─────────────────┼──────────┼──────────┼──────────┤
│ 集群 │ 支持 │ 支持 │ 支持 │
├─────────────────┼──────────┼──────────┼──────────┤
│ 事务 │ 支持 │ 不支持 │ 不支持 │
├─────────────────┼──────────┼──────────┼──────────┤
│ Lua脚本 │ 支持 │ 不支持 │ 不支持 │
├─────────────────┼──────────┼──────────┼──────────┤
│ 发布订阅 │ 支持 │ 不支持 │ 不支持 │
└─────────────────┴──────────┴──────────┴──────────┘
2. Redis 数据结构
2.1 数据结构概览
yaml
Redis 数据结构
String (字符串)
├─ 二进制安全
├─ 最大512MB
└─ 应用: 缓存、计数器、分布式锁
Hash (哈希表)
├─ 键值对集合
├─ 适合存储对象
└─ 应用: 用户信息、商品详情
List (列表)
├─ 双向链表
├─ 有序可重复
└─ 应用: 消息队列、最新列表
Set (集合)
├─ 无序不重复
├─ 支持集合运算
└─ 应用: 标签、共同好友
ZSet (有序集合)
├─ 有序不重复
├─ 每个元素关联分数
└─ 应用: 排行榜、延迟队列
特殊类型:
├─ Bitmap: 位图,统计活跃用户
├─ HyperLogLog: 基数统计
├─ Geo: 地理位置
└─ Stream: 消息流(5.0+)
2.2 底层数据结构
yaml
Redis 底层实现
String
├─ int: 整数值
├─ embstr: 短字符串(<=44字节)
└─ raw: 长字符串
Hash
├─ ziplist: 元素少且小
└─ hashtable: 元素多或大
List
├─ ziplist: 元素少且小
└─ quicklist: 默认实现
Set
├─ intset: 整数且元素少
└─ hashtable: 其他情况
ZSet
├─ ziplist: 元素少且小
└─ skiplist + hashtable: 其他情况
跳表结构 (ZSet):
Level 3: 1 ─────────────────────▶ 9
Level 2: 1 ────────▶ 5 ─────────▶ 9
Level 1: 1 ───▶ 3 ──▶ 5 ───▶ 7 ──▶ 9
Level 0: 1 ─▶ 2 ─▶ 3 ─▶ 4 ─▶ 5 ─▶ 6 ─▶ 7 ─▶ 8 ─▶ 9
3. Java 客户端集成
3.1 Maven 依赖
xml
<!-- pom.xml -->
<properties>
<jedis.version>5.0.2</jedis.version>
<lettuce.version>6.3.0.RELEASE</lettuce.version>
<redisson.version>3.24.3</redisson.version>
</properties>
<dependencies>
<!-- Jedis 客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<!-- Lettuce 客户端 (Spring Boot 默认) -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>${lettuce.version}</version>
</dependency>
<!-- Redisson 客户端 (功能丰富) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
<!-- JSON 序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version>
</dependency>
</dependencies>
3.2 Jedis 基础使用
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
import java.util.*;
/**
* Jedis 基础操作示例
*/
public class JedisExample {
private static JedisPool jedisPool;
/**
* 初始化连接池
*/
public static void initPool() {
JedisPoolConfig config = new JedisPoolConfig();
// 连接池配置
config.setMaxTotal(100); // 最大连接数
config.setMaxIdle(20); // 最大空闲连接
config.setMinIdle(5); // 最小空闲连接
config.setMaxWait(Duration.ofSeconds(10)); // 最大等待时间
// 连接测试
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
config.setTestWhileIdle(true);
jedisPool = new JedisPool(config, "localhost", 6379, 3000, null);
}
/**
* 获取 Jedis 连接
*/
public static Jedis getJedis() {
return jedisPool.getResource();
}
/**
* String 操作
*/
public static void stringOperations() {
try (Jedis jedis = getJedis()) {
// 设置值
jedis.set("name", "Redis");
// 设置带过期时间
jedis.setex("session:1001", 3600, "user_data");
// NX: 不存在才设置, XX: 存在才设置
jedis.set("lock:order", "1",
new redis.clients.jedis.params.SetParams()
.nx().ex(30));
// 获取值
String value = jedis.get("name");
System.out.println("name = " + value);
// 自增
jedis.set("counter", "0");
Long count = jedis.incr("counter");
System.out.println("counter = " + count);
// 批量操作
jedis.mset("key1", "value1", "key2", "value2");
List<String> values = jedis.mget("key1", "key2");
System.out.println("mget = " + values);
}
}
/**
* Hash 操作
*/
public static void hashOperations() {
try (Jedis jedis = getJedis()) {
String key = "user:1001";
// 设置字段
jedis.hset(key, "name", "张三");
jedis.hset(key, "age", "25");
jedis.hset(key, "city", "北京");
// 批量设置
Map<String, String> userMap = new HashMap<>();
userMap.put("email", "zhangsan@example.com");
userMap.put("phone", "13800138000");
jedis.hset(key, userMap);
// 获取字段
String name = jedis.hget(key, "name");
System.out.println("name = " + name);
// 获取所有字段
Map<String, String> user = jedis.hgetAll(key);
System.out.println("user = " + user);
// 字段自增
jedis.hincrBy(key, "age", 1);
// 判断字段是否存在
boolean exists = jedis.hexists(key, "name");
System.out.println("name exists = " + exists);
}
}
/**
* List 操作
*/
public static void listOperations() {
try (Jedis jedis = getJedis()) {
String key = "queue:tasks";
// 左侧插入
jedis.lpush(key, "task1", "task2", "task3");
// 右侧插入
jedis.rpush(key, "task4", "task5");
// 获取范围元素
List<String> tasks = jedis.lrange(key, 0, -1);
System.out.println("tasks = " + tasks);
// 弹出元素
String task = jedis.rpop(key);
System.out.println("popped = " + task);
// 阻塞弹出 (消息队列常用)
// List<String> result = jedis.brpop(10, key);
// 获取长度
Long length = jedis.llen(key);
System.out.println("length = " + length);
// 裁剪列表 (保留最新N条)
jedis.ltrim(key, 0, 99);
}
}
/**
* Set 操作
*/
public static void setOperations() {
try (Jedis jedis = getJedis()) {
// 添加元素
jedis.sadd("tags:1001", "java", "redis", "mysql");
jedis.sadd("tags:1002", "java", "python", "kafka");
// 获取所有元素
Set<String> tags = jedis.smembers("tags:1001");
System.out.println("tags = " + tags);
// 判断是否存在
boolean isMember = jedis.sismember("tags:1001", "java");
System.out.println("is member = " + isMember);
// 集合运算
// 交集 (共同标签)
Set<String> inter = jedis.sinter("tags:1001", "tags:1002");
System.out.println("intersection = " + inter);
// 并集
Set<String> union = jedis.sunion("tags:1001", "tags:1002");
System.out.println("union = " + union);
// 差集
Set<String> diff = jedis.sdiff("tags:1001", "tags:1002");
System.out.println("difference = " + diff);
// 随机获取元素
String random = jedis.srandmember("tags:1001");
System.out.println("random = " + random);
}
}
/**
* ZSet (有序集合) 操作
*/
public static void zsetOperations() {
try (Jedis jedis = getJedis()) {
String key = "leaderboard";
// 添加元素
jedis.zadd(key, 100, "player1");
jedis.zadd(key, 200, "player2");
jedis.zadd(key, 150, "player3");
// 批量添加
Map<String, Double> scores = new HashMap<>();
scores.put("player4", 180.0);
scores.put("player5", 220.0);
jedis.zadd(key, scores);
// 获取排名 (从0开始,按分数升序)
Long rank = jedis.zrank(key, "player2");
System.out.println("player2 rank = " + rank);
// 获取逆序排名 (分数高的排前面)
Long revRank = jedis.zrevrank(key, "player2");
System.out.println("player2 rev rank = " + revRank);
// 获取分数
Double score = jedis.zscore(key, "player2");
System.out.println("player2 score = " + score);
// 获取排行榜 (Top N)
List<String> top3 = jedis.zrevrange(key, 0, 2);
System.out.println("Top 3 = " + top3);
// 带分数获取
List<redis.clients.jedis.resps.Tuple> top3WithScores =
jedis.zrevrangeWithScores(key, 0, 2);
for (redis.clients.jedis.resps.Tuple tuple : top3WithScores) {
System.out.printf("%s: %.0f\n",
tuple.getElement(), tuple.getScore());
}
// 增加分数
jedis.zincrby(key, 50, "player1");
// 按分数范围获取
List<String> range = jedis.zrangeByScore(key, 100, 200);
System.out.println("score 100-200 = " + range);
}
}
public static void main(String[] args) {
initPool();
System.out.println("=== String Operations ===");
stringOperations();
System.out.println("\n=== Hash Operations ===");
hashOperations();
System.out.println("\n=== List Operations ===");
listOperations();
System.out.println("\n=== Set Operations ===");
setOperations();
System.out.println("\n=== ZSet Operations ===");
zsetOperations();
jedisPool.close();
}
}
3.3 Lettuce 异步操作
java
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.sync.RedisCommands;
import java.util.concurrent.CompletableFuture;
/**
* Lettuce 客户端示例
*/
public class LettuceExample {
/**
* 同步操作
*/
public static void syncOperations() {
RedisClient client = RedisClient.create("redis://localhost:6379");
StatefulRedisConnection<String, String> connection = client.connect();
RedisCommands<String, String> commands = connection.sync();
// 同步操作
commands.set("key", "value");
String value = commands.get("key");
System.out.println("value = " + value);
connection.close();
client.shutdown();
}
/**
* 异步操作
*/
public static void asyncOperations() {
RedisClient client = RedisClient.create("redis://localhost:6379");
StatefulRedisConnection<String, String> connection = client.connect();
RedisAsyncCommands<String, String> commands = connection.async();
// 异步操作
CompletableFuture<String> future = commands.set("async-key", "async-value")
.toCompletableFuture()
.thenCompose(result -> commands.get("async-key").toCompletableFuture());
future.thenAccept(value -> {
System.out.println("Async value = " + value);
}).join();
connection.close();
client.shutdown();
}
/**
* 响应式操作
*/
public static void reactiveOperations() {
RedisClient client = RedisClient.create("redis://localhost:6379");
StatefulRedisConnection<String, String> connection = client.connect();
var commands = connection.reactive();
// 响应式操作
commands.set("reactive-key", "reactive-value")
.then(commands.get("reactive-key"))
.subscribe(value -> {
System.out.println("Reactive value = " + value);
});
// 等待完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
connection.close();
client.shutdown();
}
public static void main(String[] args) {
syncOperations();
asyncOperations();
reactiveOperations();
}
}
4. 缓存实战场景
4.1 多级缓存架构
markdown
多级缓存架构
请求
│
▼
┌──────────┐
│ 本地缓存 │ L1 Cache (Caffeine/Guava)
│ (进程内) │ 延迟: <1ms
└────┬─────┘
│ Miss
▼
┌──────────┐
│ Redis │ L2 Cache (分布式)
│ (分布式) │ 延迟: 1-5ms
└────┬─────┘
│ Miss
▼
┌──────────┐
│ Database │ 数据源
│ (MySQL) │ 延迟: 10-100ms
└──────────┘
优势:
1. 本地缓存减少网络开销
2. Redis 保证数据一致性
3. 多层防护,高可用
java
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
* 多级缓存实现
*/
public class MultiLevelCache {
private final Cache<String, Object> localCache;
private final JedisPool jedisPool;
private final ObjectMapper objectMapper = new ObjectMapper();
public MultiLevelCache(JedisPool jedisPool) {
this.jedisPool = jedisPool;
// 本地缓存配置
this.localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
/**
* 获取缓存
*/
public <T> T get(String key, Class<T> type,
java.util.function.Supplier<T> dbLoader) {
// 1. 查询本地缓存
T value = (T) localCache.getIfPresent(key);
if (value != null) {
System.out.println("L1 Cache Hit: " + key);
return value;
}
// 2. 查询 Redis 缓存
try (Jedis jedis = jedisPool.getResource()) {
String json = jedis.get(key);
if (json != null) {
System.out.println("L2 Cache Hit: " + key);
value = objectMapper.readValue(json, type);
// 回填本地缓存
localCache.put(key, value);
return value;
}
} catch (Exception e) {
System.err.println("Redis get error: " + e.getMessage());
}
// 3. 查询数据库
System.out.println("Cache Miss, loading from DB: " + key);
value = dbLoader.get();
if (value != null) {
// 回填缓存
put(key, value, 3600);
}
return value;
}
/**
* 设置缓存
*/
public void put(String key, Object value, int expireSeconds) {
// 写入本地缓存
localCache.put(key, value);
// 写入 Redis
try (Jedis jedis = jedisPool.getResource()) {
String json = objectMapper.writeValueAsString(value);
jedis.setex(key, expireSeconds, json);
} catch (Exception e) {
System.err.println("Redis set error: " + e.getMessage());
}
}
/**
* 删除缓存
*/
public void delete(String key) {
// 删除本地缓存
localCache.invalidate(key);
// 删除 Redis
try (Jedis jedis = jedisPool.getResource()) {
jedis.del(key);
}
}
/**
* 使用示例
*/
public static void main(String[] args) {
JedisPool jedisPool = new JedisPool("localhost", 6379);
MultiLevelCache cache = new MultiLevelCache(jedisPool);
// 获取用户信息
User user = cache.get("user:1001", User.class, () -> {
// 模拟数据库查询
return new User(1001L, "张三", "zhangsan@example.com");
});
System.out.println("User: " + user);
jedisPool.close();
}
static class User {
private Long id;
private String name;
private String email;
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
@Override
public String toString() {
return String.format("User{id=%d, name='%s', email='%s'}",
id, name, email);
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
}
4.2 缓存更新策略
java
/**
* 缓存更新策略
*/
public class CacheUpdateStrategies {
private final JedisPool jedisPool;
public CacheUpdateStrategies(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 策略1: Cache Aside (旁路缓存)
* 读: 先读缓存,没有则读数据库,回填缓存
* 写: 先更新数据库,再删除缓存
*/
public User getUserCacheAside(Long userId) {
String key = "user:" + userId;
try (Jedis jedis = jedisPool.getResource()) {
// 1. 查询缓存
String json = jedis.get(key);
if (json != null) {
return parseUser(json);
}
// 2. 查询数据库
User user = getUserFromDB(userId);
// 3. 回填缓存
if (user != null) {
jedis.setex(key, 3600, toJson(user));
}
return user;
}
}
public void updateUserCacheAside(User user) {
// 1. 先更新数据库
updateUserInDB(user);
// 2. 再删除缓存
try (Jedis jedis = jedisPool.getResource()) {
jedis.del("user:" + user.getId());
}
// 注意: 删除缓存而非更新缓存
// 避免并发写导致的数据不一致
}
/**
* 策略2: Read/Write Through (读写穿透)
* 缓存作为主要数据源,由缓存组件负责读写数据库
*/
public User getUserReadThrough(Long userId) {
String key = "user:" + userId;
try (Jedis jedis = jedisPool.getResource()) {
String json = jedis.get(key);
if (json != null) {
return parseUser(json);
}
// 缓存组件负责从数据库加载
User user = getUserFromDB(userId);
if (user != null) {
jedis.setex(key, 3600, toJson(user));
}
return user;
}
}
/**
* 策略3: Write Behind (异步写回)
* 写操作只更新缓存,异步批量写入数据库
*/
public void updateUserWriteBehind(User user) {
String key = "user:" + user.getId();
try (Jedis jedis = jedisPool.getResource()) {
// 1. 只更新缓存
jedis.setex(key, 3600, toJson(user));
// 2. 加入异步写队列
jedis.lpush("write_back_queue", toJson(user));
}
// 异步任务定期处理队列,批量写入数据库
}
/**
* 延迟双删策略
* 解决数据库主从延迟导致的缓存不一致
*/
public void updateUserDelayedDoubleDelete(User user) {
String key = "user:" + user.getId();
try (Jedis jedis = jedisPool.getResource()) {
// 1. 先删除缓存
jedis.del(key);
// 2. 更新数据库
updateUserInDB(user);
// 3. 延迟再次删除缓存 (异步)
CompletableFuture.runAsync(() -> {
try {
// 等待主从同步
Thread.sleep(500);
jedis.del(key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
// 辅助方法
private User getUserFromDB(Long userId) {
// 模拟数据库查询
return new User(userId, "User" + userId, "user@example.com");
}
private void updateUserInDB(User user) {
// 模拟数据库更新
System.out.println("Updated user in DB: " + user.getId());
}
private User parseUser(String json) {
// JSON 解析
return new User();
}
private String toJson(User user) {
// JSON 序列化
return "{}";
}
static class User {
private Long id;
private String name;
private String email;
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
}
5. 分布式锁实现
5.1 基础分布式锁
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.Collections;
import java.util.UUID;
/**
* Redis 分布式锁实现
*/
public class RedisDistributedLock {
private final JedisPool jedisPool;
private final String lockKey;
private final String lockValue;
private final int expireSeconds;
public RedisDistributedLock(JedisPool jedisPool, String lockKey,
int expireSeconds) {
this.jedisPool = jedisPool;
this.lockKey = "lock:" + lockKey;
this.lockValue = UUID.randomUUID().toString();
this.expireSeconds = expireSeconds;
}
/**
* 获取锁
*/
public boolean tryLock() {
try (Jedis jedis = jedisPool.getResource()) {
// SET key value NX EX seconds
String result = jedis.set(lockKey, lockValue,
SetParams.setParams().nx().ex(expireSeconds));
return "OK".equals(result);
}
}
/**
* 获取锁 (带重试)
*/
public boolean tryLock(int retryTimes, long retryIntervalMs) {
for (int i = 0; i < retryTimes; i++) {
if (tryLock()) {
return true;
}
try {
Thread.sleep(retryIntervalMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 释放锁 (使用 Lua 脚本保证原子性)
*/
public boolean unlock() {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
try (Jedis jedis = jedisPool.getResource()) {
Object result = jedis.eval(luaScript,
Collections.singletonList(lockKey),
Collections.singletonList(lockValue));
return Long.valueOf(1L).equals(result);
}
}
/**
* 使用示例
*/
public static void main(String[] args) {
JedisPool jedisPool = new JedisPool("localhost", 6379);
RedisDistributedLock lock =
new RedisDistributedLock(jedisPool, "order:create", 30);
try {
if (lock.tryLock(3, 100)) {
System.out.println("获取锁成功,执行业务逻辑...");
// 执行业务逻辑
Thread.sleep(1000);
System.out.println("业务逻辑执行完成");
} else {
System.out.println("获取锁失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("锁已释放");
}
jedisPool.close();
}
}
5.2 Redisson 分布式锁
java
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
/**
* Redisson 分布式锁
* 支持: 可重入、公平锁、读写锁、红锁等
*/
public class RedissonLockExample {
private static RedissonClient redisson;
/**
* 初始化 Redisson
*/
public static void initRedisson() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setDatabase(0)
.setConnectionPoolSize(10)
.setConnectionMinimumIdleSize(5);
redisson = Redisson.create(config);
}
/**
* 基本锁操作
*/
public static void basicLock() {
RLock lock = redisson.getLock("myLock");
try {
// 获取锁,最多等待10秒,锁定后30秒自动释放
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (locked) {
System.out.println("获取锁成功");
// 执行业务逻辑
Thread.sleep(5000);
} else {
System.out.println("获取锁失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 只有锁持有者才能释放
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("锁已释放");
}
}
}
/**
* 可重入锁
*/
public static void reentrantLock() {
RLock lock = redisson.getLock("reentrantLock");
try {
lock.lock();
System.out.println("第一次获取锁");
// 可重入: 同一线程可以再次获取
lock.lock();
System.out.println("第二次获取锁 (可重入)");
lock.unlock();
System.out.println("第一次释放");
lock.unlock();
System.out.println("第二次释放");
} finally {
while (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 公平锁
*/
public static void fairLock() {
RLock fairLock = redisson.getFairLock("fairLock");
try {
// 按请求顺序获取锁
fairLock.lock();
System.out.println("获取公平锁成功");
// 执行业务逻辑
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
fairLock.unlock();
}
}
/**
* 读写锁
*/
public static void readWriteLock() {
RReadWriteLock rwLock = redisson.getReadWriteLock("rwLock");
// 读锁 (共享锁)
RLock readLock = rwLock.readLock();
// 写锁 (排他锁)
RLock writeLock = rwLock.writeLock();
// 读操作 (多个线程可以同时读)
new Thread(() -> {
try {
readLock.lock();
System.out.println("读线程1获取读锁");
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
readLock.unlock();
System.out.println("读线程1释放读锁");
}
}).start();
// 写操作 (必须等所有读锁释放)
new Thread(() -> {
try {
writeLock.lock();
System.out.println("写线程获取写锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
writeLock.unlock();
System.out.println("写线程释放写锁");
}
}).start();
}
/**
* 看门狗自动续期
*/
public static void watchdogDemo() {
RLock lock = redisson.getLock("watchdogLock");
try {
// 不指定过期时间,看门狗会自动续期
lock.lock();
System.out.println("获取锁成功,看门狗自动续期");
// 模拟长时间业务
Thread.sleep(35000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
initRedisson();
basicLock();
// reentrantLock();
// fairLock();
// readWriteLock();
// watchdogDemo();
redisson.shutdown();
}
}
5.3 分布式锁最佳实践
markdown
分布式锁注意事项
1. 锁超时问题
┌────────────┐
│ 获取锁 │ 设置超时时间
├────────────┤
│ 执行业务 │ 如果超时会自动释放
├────────────┤
│ 释放锁 │ 可能释放了别人的锁
└────────────┘
解决:
- 使用唯一标识
- Redisson 看门狗自动续期
2. 锁不可重入
Thread A: lock() -> lock() -> 死锁
解决: 使用可重入锁
3. 单点故障
Redis 主节点宕机,锁丢失
解决:
- Red Lock 算法
- Redis 集群
4. 锁释放失败
业务异常,锁未释放
解决:
- 设置过期时间
- finally 中释放
6. 消息队列与发布订阅
6.1 List 实现消息队列
java
/**
* Redis List 实现简单消息队列
*/
public class RedisMessageQueue {
private final JedisPool jedisPool;
private final String queueName;
public RedisMessageQueue(JedisPool jedisPool, String queueName) {
this.jedisPool = jedisPool;
this.queueName = queueName;
}
/**
* 发送消息
*/
public void send(String message) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.lpush(queueName, message);
System.out.println("消息已发送: " + message);
}
}
/**
* 阻塞接收消息
*/
public String receive(int timeoutSeconds) {
try (Jedis jedis = jedisPool.getResource()) {
List<String> result = jedis.brpop(timeoutSeconds, queueName);
if (result != null && result.size() > 1) {
return result.get(1);
}
return null;
}
}
/**
* 可靠队列 (带确认)
*/
public String receiveReliable(int timeoutSeconds) {
try (Jedis jedis = jedisPool.getResource()) {
// 从队列取出并放入处理中队列
String message = jedis.brpoplpush(
queueName,
queueName + ":processing",
timeoutSeconds
);
return message;
}
}
/**
* 确认消息处理完成
*/
public void ack(String message) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.lrem(queueName + ":processing", 1, message);
}
}
/**
* 延迟队列 (使用 ZSet)
*/
public void sendDelayed(String message, long delaySeconds) {
try (Jedis jedis = jedisPool.getResource()) {
double score = System.currentTimeMillis() / 1000.0 + delaySeconds;
jedis.zadd(queueName + ":delayed", score, message);
System.out.println("延迟消息已发送,延迟: " + delaySeconds + "秒");
}
}
/**
* 处理延迟消息
*/
public void processDelayed() {
try (Jedis jedis = jedisPool.getResource()) {
while (true) {
double now = System.currentTimeMillis() / 1000.0;
// 获取到期的消息
List<String> messages = jedis.zrangeByScore(
queueName + ":delayed", 0, now, 0, 10);
if (messages.isEmpty()) {
Thread.sleep(100);
continue;
}
for (String message : messages) {
// 移动到主队列
Long removed = jedis.zrem(queueName + ":delayed", message);
if (removed > 0) {
jedis.lpush(queueName, message);
System.out.println("延迟消息已到期: " + message);
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws Exception {
JedisPool jedisPool = new JedisPool("localhost", 6379);
RedisMessageQueue queue = new RedisMessageQueue(jedisPool, "task_queue");
// 生产者
new Thread(() -> {
for (int i = 0; i < 10; i++) {
queue.send("Task " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
// 消费者
new Thread(() -> {
while (true) {
String message = queue.receive(5);
if (message != null) {
System.out.println("处理消息: " + message);
}
}
}).start();
Thread.sleep(10000);
jedisPool.close();
}
}
6.2 发布订阅
java
import redis.clients.jedis.JedisPubSub;
/**
* Redis 发布订阅
*/
public class RedisPubSub {
private final JedisPool jedisPool;
public RedisPubSub(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 发布消息
*/
public void publish(String channel, String message) {
try (Jedis jedis = jedisPool.getResource()) {
Long receivers = jedis.publish(channel, message);
System.out.printf("消息发布到 %s,接收者: %d\n", channel, receivers);
}
}
/**
* 订阅频道
*/
public void subscribe(String... channels) {
new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
System.out.printf("收到消息 [%s]: %s\n", channel, message);
// 处理消息
processMessage(channel, message);
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
System.out.printf("订阅成功: %s,当前订阅数: %d\n",
channel, subscribedChannels);
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
System.out.printf("取消订阅: %s,当前订阅数: %d\n",
channel, subscribedChannels);
}
}, channels);
}
}).start();
}
/**
* 模式订阅
*/
public void psubscribe(String... patterns) {
new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.psubscribe(new JedisPubSub() {
@Override
public void onPMessage(String pattern, String channel,
String message) {
System.out.printf("模式匹配 [%s] 频道 [%s]: %s\n",
pattern, channel, message);
}
}, patterns);
}
}).start();
}
private void processMessage(String channel, String message) {
// 业务处理
System.out.println("处理消息: " + message);
}
public static void main(String[] args) throws Exception {
JedisPool jedisPool = new JedisPool("localhost", 6379);
RedisPubSub pubSub = new RedisPubSub(jedisPool);
// 订阅频道
pubSub.subscribe("news", "events");
// 模式订阅
pubSub.psubscribe("order:*");
Thread.sleep(1000);
// 发布消息
pubSub.publish("news", "今日新闻");
pubSub.publish("events", "系统事件");
pubSub.publish("order:created", "订单创建");
pubSub.publish("order:paid", "订单支付");
Thread.sleep(2000);
jedisPool.close();
}
}
6.3 Stream 消息队列 (Redis 5.0+)
java
import redis.clients.jedis.StreamEntry;
import redis.clients.jedis.StreamEntryID;
import redis.clients.jedis.params.XAddParams;
import redis.clients.jedis.params.XReadGroupParams;
import redis.clients.jedis.resps.StreamConsumersInfo;
import redis.clients.jedis.resps.StreamGroupInfo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Redis Stream 消息队列
* 特点: 持久化、消费者组、消息确认
*/
public class RedisStreamQueue {
private final JedisPool jedisPool;
private final String streamKey;
public RedisStreamQueue(JedisPool jedisPool, String streamKey) {
this.jedisPool = jedisPool;
this.streamKey = streamKey;
}
/**
* 创建消费者组
*/
public void createConsumerGroup(String groupName) {
try (Jedis jedis = jedisPool.getResource()) {
try {
jedis.xgroupCreate(streamKey, groupName,
StreamEntryID.LAST_ENTRY, true);
System.out.println("消费者组创建成功: " + groupName);
} catch (Exception e) {
if (e.getMessage().contains("BUSYGROUP")) {
System.out.println("消费者组已存在: " + groupName);
} else {
throw e;
}
}
}
}
/**
* 发送消息
*/
public String send(Map<String, String> message) {
try (Jedis jedis = jedisPool.getResource()) {
StreamEntryID id = jedis.xadd(streamKey,
XAddParams.xAddParams().maxLen(10000), message);
System.out.println("消息已发送,ID: " + id);
return id.toString();
}
}
/**
* 消费者组消费消息
*/
public List<Map.Entry<String, List<StreamEntry>>> consume(
String groupName, String consumerName, int count, long blockMs) {
try (Jedis jedis = jedisPool.getResource()) {
Map<String, StreamEntryID> streams = new HashMap<>();
streams.put(streamKey, StreamEntryID.UNRECEIVED_ENTRY);
return jedis.xreadGroup(groupName, consumerName,
XReadGroupParams.xReadGroupParams()
.count(count)
.block((int) blockMs),
streams);
}
}
/**
* 确认消息
*/
public void ack(String groupName, String... messageIds) {
try (Jedis jedis = jedisPool.getResource()) {
StreamEntryID[] ids = new StreamEntryID[messageIds.length];
for (int i = 0; i < messageIds.length; i++) {
ids[i] = new StreamEntryID(messageIds[i]);
}
long acked = jedis.xack(streamKey, groupName, ids);
System.out.println("确认消息数: " + acked);
}
}
/**
* 获取未确认消息 (用于重试)
*/
public List<StreamEntry> getPendingMessages(String groupName,
String consumerName,
int count) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.xclaim(streamKey, groupName, consumerName,
60000, // 最小空闲时间
XClaimParams.xClaimParams().count(count),
StreamEntryID.MINIMUM_ID);
}
}
/**
* 查看 Stream 信息
*/
public void printStreamInfo() {
try (Jedis jedis = jedisPool.getResource()) {
// 消费者组信息
List<StreamGroupInfo> groups = jedis.xinfoGroups(streamKey);
System.out.println("\n消费者组:");
for (StreamGroupInfo group : groups) {
System.out.printf(" 组: %s, 消费者: %d, 待处理: %d\n",
group.getName(),
group.getConsumers(),
group.getPending());
}
}
}
public static void main(String[] args) throws Exception {
JedisPool jedisPool = new JedisPool("localhost", 6379);
RedisStreamQueue queue = new RedisStreamQueue(jedisPool, "orders");
// 创建消费者组
queue.createConsumerGroup("order-processor");
// 生产者发送消息
for (int i = 0; i < 5; i++) {
Map<String, String> message = new HashMap<>();
message.put("orderId", "ORDER_" + i);
message.put("userId", "USER_" + (i % 3));
message.put("amount", String.valueOf(100 + i * 10));
queue.send(message);
}
// 消费者消费消息
new Thread(() -> {
while (true) {
var results = queue.consume("order-processor",
"consumer-1", 10, 5000);
if (results == null || results.isEmpty()) {
continue;
}
for (var entry : results) {
List<StreamEntry> messages = entry.getValue();
for (StreamEntry message : messages) {
System.out.printf("处理消息 [%s]: %s\n",
message.getID(), message.getFields());
// 处理完成后确认
queue.ack("order-processor",
message.getID().toString());
}
}
}
}).start();
Thread.sleep(5000);
queue.printStreamInfo();
jedisPool.close();
}
}
7. 持久化机制
7.1 RDB 与 AOF
yaml
持久化机制对比
RDB (Redis Database)
┌──────────────────────────────────────┐
│ 特点: │
│ - 二进制快照文件 │
│ - 文件小,恢复快 │
│ - fork 子进程,可能阻塞 │
│ - 可能丢失最后一次快照后的数据 │
│ │
│ 触发条件: │
│ - save 900 1 # 900秒内1次修改 │
│ - save 300 10 # 300秒内10次修改 │
│ - save 60 10000 # 60秒内10000次修改 │
│ - 执行 BGSAVE 命令 │
│ - 主从复制时 │
└──────────────────────────────────────┘
AOF (Append Only File)
┌──────────────────────────────────────┐
│ 特点: │
│ - 追加写入命令日志 │
│ - 数据安全性高 │
│ - 文件大,恢复慢 │
│ - 支持重写压缩 │
│ │
│ 同步策略: │
│ - always: 每条命令都同步 │
│ - everysec: 每秒同步 (推荐) │
│ - no: 操作系统决定 │
│ │
│ 重写触发: │
│ - auto-aof-rewrite-percentage 100 │
│ - auto-aof-rewrite-min-size 64mb │
└──────────────────────────────────────┘
混合持久化 (Redis 4.0+)
┌──────────────────────────────────────┐
│ RDB 头 (全量) + AOF 尾 (增量) │
│ │
│ 优势: │
│ - 快速加载 RDB 部分 │
│ - AOF 保证数据完整 │
│ │
│ 配置: │
│ aof-use-rdb-preamble yes │
└──────────────────────────────────────┘
7.2 持久化配置
java
/**
* Redis 持久化配置示例
*/
public class RedisPersistenceConfig {
/**
* RDB 配置
*/
public static String getRDBConfig() {
return """
# RDB 配置
save 900 1
save 300 10
save 60 10000
# RDB 文件名
dbfilename dump.rdb
# RDB 文件目录
dir /var/lib/redis
# 压缩 RDB 文件
rdbcompression yes
# RDB 校验
rdbchecksum yes
# 后台保存失败时停止写入
stop-writes-on-bgsave-error yes
""";
}
/**
* AOF 配置
*/
public static String getAOFConfig() {
return """
# 启用 AOF
appendonly yes
# AOF 文件名
appendfilename "appendonly.aof"
# 同步策略
appendfsync everysec
# 重写时是否同步
no-appendfsync-on-rewrite no
# 自动重写条件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 加载时忽略错误
aof-load-truncated yes
# 混合持久化
aof-use-rdb-preamble yes
""";
}
/**
* 手动触发持久化
*/
public static void triggerPersistence(Jedis jedis) {
// 异步 RDB 快照
String result = jedis.bgsave();
System.out.println("BGSAVE: " + result);
// 同步 RDB 快照 (阻塞)
// jedis.save();
// 异步 AOF 重写
jedis.bgrewriteaof();
System.out.println("BGREWRITEAOF started");
}
/**
* 检查持久化状态
*/
public static void checkPersistenceStatus(Jedis jedis) {
String info = jedis.info("persistence");
System.out.println("持久化状态:\n" + info);
}
}
8. 集群架构
8.1 主从复制
markdown
主从复制架构
┌──────────┐
│ Master │
│ (写入) │
└────┬─────┘
│
┌──────┼──────┐
│ │ │
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│Slave1│ │Slave2│ │Slave3│
│(读取)│ │(读取)│ │(读取)│
└──────┘ └──────┘ └──────┘
特点:
1. 异步复制
2. 读写分离
3. 从节点只读
4. 全量同步 + 增量同步
8.2 哨兵模式
markdown
哨兵模式架构
┌──────────┐ ┌──────────┐ ┌──────────┐
│Sentinel 1│ │Sentinel 2│ │Sentinel 3│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└────────────────┼────────────────┘
│
▼
┌──────────┐
│ Master │
└────┬─────┘
│
┌─────────┼─────────┐
│ │ │
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│Slave1│ │Slave2│ │Slave3│
└──────┘ └──────┘ └──────┘
功能:
1. 监控: 检查节点状态
2. 通知: 发送故障通知
3. 自动故障转移: 主节点故障时自动选举
4. 配置提供: 客户端获取主节点地址
java
import redis.clients.jedis.JedisSentinelPool;
/**
* 哨兵模式连接
*/
public class SentinelExample {
public static JedisSentinelPool createSentinelPool() {
Set<String> sentinels = new HashSet<>();
sentinels.add("sentinel1:26379");
sentinels.add("sentinel2:26379");
sentinels.add("sentinel3:26379");
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(20);
config.setMinIdle(5);
return new JedisSentinelPool(
"mymaster", // 主节点名称
sentinels,
config,
3000, // 超时时间
null // 密码
);
}
public static void main(String[] args) {
JedisSentinelPool pool = createSentinelPool();
try (Jedis jedis = pool.getResource()) {
jedis.set("key", "value");
String value = jedis.get("key");
System.out.println("value = " + value);
}
pool.close();
}
}
8.3 Cluster 集群
markdown
Redis Cluster 架构
┌─────────────────────────────────────────────────┐
│ Slot: 0 - 16383 │
├─────────────┬─────────────┬─────────────────────┤
│ 0 - 5460 │ 5461-10922 │ 10923 - 16383 │
│ │ │ │
│ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │
│ │ Master1 │ │ │ Master2 │ │ │ Master3 │ │
│ └────┬────┘ │ └────┬────┘ │ └────┬────┘ │
│ │ │ │ │ │ │
│ ┌────▼────┐ │ ┌────▼────┐ │ ┌────▼────┐ │
│ │ Slave1 │ │ │ Slave2 │ │ │ Slave3 │ │
│ └─────────┘ │ └─────────┘ │ └─────────┘ │
└─────────────┴─────────────┴─────────────────────┘
特点:
1. 数据分片: 16384 个槽
2. 去中心化: 无需哨兵
3. 高可用: 节点故障自动转移
4. 线性扩展: 添加节点自动迁移
java
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;
/**
* Cluster 集群连接
*/
public class ClusterExample {
public static JedisCluster createCluster() {
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("node1", 6379));
nodes.add(new HostAndPort("node2", 6379));
nodes.add(new HostAndPort("node3", 6379));
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(20);
return new JedisCluster(
nodes,
3000, // 连接超时
3000, // 读取超时
5, // 最大重试次数
null, // 密码
config
);
}
public static void main(String[] args) {
try (JedisCluster cluster = createCluster()) {
// 操作与单机相同
cluster.set("key", "value");
String value = cluster.get("key");
System.out.println("value = " + value);
// 批量操作需要使用 {} 指定同一个槽
cluster.mset("{user}:1:name", "张三",
"{user}:1:age", "25");
}
}
}
9. 性能优化
9.1 优化建议
markdown
Redis 性能优化清单
1. 内存优化
┌─────────────────────────────────┐
│ - 选择合适的数据结构 │
│ - 设置合理的过期时间 │
│ - 启用内存淘汰策略 │
│ - 使用压缩数据 │
└─────────────────────────────────┘
2. 网络优化
┌─────────────────────────────────┐
│ - 使用 Pipeline 批量操作 │
│ - 减少网络往返 │
│ - 使用连接池 │
│ - 启用 TCP_NODELAY │
└─────────────────────────────────┘
3. 命令优化
┌─────────────────────────────────┐
│ - 避免使用 KEYS 命令 │
│ - 使用 SCAN 替代 │
│ - 避免大 Key │
│ - 避免热点 Key │
└─────────────────────────────────┘
4. 持久化优化
┌─────────────────────────────────┐
│ - 根据场景选择 RDB/AOF │
│ - 关闭不必要的持久化 │
│ - 使用 SSD 磁盘 │
└─────────────────────────────────┘
9.2 Pipeline 批量操作
java
/**
* Pipeline 批量操作
*/
public class PipelineExample {
/**
* 普通方式 vs Pipeline
*/
public static void comparePerformance(JedisPool jedisPool) {
int count = 10000;
// 普通方式
long start = System.currentTimeMillis();
try (Jedis jedis = jedisPool.getResource()) {
for (int i = 0; i < count; i++) {
jedis.set("key:" + i, "value:" + i);
}
}
long normalTime = System.currentTimeMillis() - start;
System.out.println("普通方式耗时: " + normalTime + "ms");
// Pipeline 方式
start = System.currentTimeMillis();
try (Jedis jedis = jedisPool.getResource()) {
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < count; i++) {
pipeline.set("pipe:" + i, "value:" + i);
}
// 执行并获取结果
List<Object> results = pipeline.syncAndReturnAll();
}
long pipelineTime = System.currentTimeMillis() - start;
System.out.println("Pipeline 方式耗时: " + pipelineTime + "ms");
System.out.printf("性能提升: %.2f 倍\n",
(double) normalTime / pipelineTime);
}
/**
* Pipeline 批量读取
*/
public static void pipelineRead(JedisPool jedisPool, List<String> keys) {
try (Jedis jedis = jedisPool.getResource()) {
Pipeline pipeline = jedis.pipelined();
for (String key : keys) {
pipeline.get(key);
}
List<Object> results = pipeline.syncAndReturnAll();
for (int i = 0; i < keys.size(); i++) {
System.out.printf("%s = %s\n", keys.get(i), results.get(i));
}
}
}
public static void main(String[] args) {
JedisPool jedisPool = new JedisPool("localhost", 6379);
comparePerformance(jedisPool);
jedisPool.close();
}
}
9.3 Lua 脚本
java
/**
* Lua 脚本示例
*/
public class LuaScriptExample {
/**
* 原子性计数器 (带上限)
*/
public static Long atomicCounter(Jedis jedis, String key,
int increment, int maxValue) {
String script =
"local current = redis.call('get', KEYS[1])\n" +
"if not current then\n" +
" current = 0\n" +
"else\n" +
" current = tonumber(current)\n" +
"end\n" +
"local new_value = current + tonumber(ARGV[1])\n" +
"if new_value > tonumber(ARGV[2]) then\n" +
" return -1\n" +
"end\n" +
"redis.call('set', KEYS[1], new_value)\n" +
"return new_value";
Object result = jedis.eval(script,
Collections.singletonList(key),
Arrays.asList(String.valueOf(increment),
String.valueOf(maxValue)));
return (Long) result;
}
/**
* 限流器 (滑动窗口)
*/
public static boolean slidingWindowRateLimiter(
Jedis jedis, String key, int maxRequests, int windowSeconds) {
String script =
"local key = KEYS[1]\n" +
"local now = tonumber(ARGV[1])\n" +
"local window = tonumber(ARGV[2])\n" +
"local max_requests = tonumber(ARGV[3])\n" +
"\n" +
"-- 移除窗口外的请求\n" +
"redis.call('zremrangebyscore', key, 0, now - window * 1000)\n" +
"\n" +
"-- 获取当前请求数\n" +
"local current = redis.call('zcard', key)\n" +
"\n" +
"if current < max_requests then\n" +
" -- 添加当前请求\n" +
" redis.call('zadd', key, now, now)\n" +
" -- 设置过期时间\n" +
" redis.call('expire', key, window)\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
Object result = jedis.eval(script,
Collections.singletonList(key),
Arrays.asList(
String.valueOf(System.currentTimeMillis()),
String.valueOf(windowSeconds),
String.valueOf(maxRequests)
));
return Long.valueOf(1L).equals(result);
}
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 测试计数器
for (int i = 0; i < 5; i++) {
Long count = atomicCounter(jedis, "counter", 10, 30);
System.out.println("Counter: " + count);
}
// 测试限流器
String userId = "user:1001";
for (int i = 0; i < 15; i++) {
boolean allowed = slidingWindowRateLimiter(
jedis, "rate:" + userId, 10, 60);
System.out.println("Request " + i + ": " +
(allowed ? "允许" : "拒绝"));
}
}
}
}
10. 常见问题与解决方案
10.1 缓存穿透
makefile
缓存穿透
请求 ──▶ 缓存 (Miss) ──▶ 数据库 (Not Found)
场景: 查询不存在的数据,每次都穿透到数据库
危害: 数据库压力大,可能被恶意攻击
解决方案:
1. 缓存空值
2. 布隆过滤器
java
/**
* 缓存穿透解决方案
*/
public class CachePenetrationSolution {
private final JedisPool jedisPool;
// 布隆过滤器 (使用 Redisson)
private RBloomFilter<Long> bloomFilter;
public CachePenetrationSolution(JedisPool jedisPool,
RedissonClient redisson) {
this.jedisPool = jedisPool;
// 初始化布隆过滤器
this.bloomFilter = redisson.getBloomFilter("user:bloom");
this.bloomFilter.tryInit(1000000, 0.01);
}
/**
* 方案1: 缓存空值
*/
public User getUserCacheNull(Long userId) {
String key = "user:" + userId;
try (Jedis jedis = jedisPool.getResource()) {
String value = jedis.get(key);
if (value != null) {
if ("NULL".equals(value)) {
return null; // 缓存的空值
}
return parseUser(value);
}
// 查询数据库
User user = getUserFromDB(userId);
if (user == null) {
// 缓存空值,设置较短过期时间
jedis.setex(key, 300, "NULL");
} else {
jedis.setex(key, 3600, toJson(user));
}
return user;
}
}
/**
* 方案2: 布隆过滤器
*/
public User getUserBloomFilter(Long userId) {
// 先检查布隆过滤器
if (!bloomFilter.contains(userId)) {
return null; // 一定不存在
}
// 可能存在,查询缓存和数据库
String key = "user:" + userId;
try (Jedis jedis = jedisPool.getResource()) {
String value = jedis.get(key);
if (value != null) {
return parseUser(value);
}
User user = getUserFromDB(userId);
if (user != null) {
jedis.setex(key, 3600, toJson(user));
}
return user;
}
}
/**
* 初始化布隆过滤器
*/
public void initBloomFilter(List<Long> userIds) {
for (Long userId : userIds) {
bloomFilter.add(userId);
}
System.out.println("布隆过滤器初始化完成: " + userIds.size());
}
private User getUserFromDB(Long userId) {
return null; // 模拟
}
private User parseUser(String json) {
return null; // 模拟
}
private String toJson(User user) {
return "{}"; // 模拟
}
static class User {
private Long id;
private String name;
}
}
10.2 缓存击穿
makefile
缓存击穿
热点Key ──▶ 过期 ──▶ 大量请求同时访问数据库
场景: 热点Key过期瞬间,大量请求打到数据库
危害: 数据库瞬时压力大
解决方案:
1. 互斥锁
2. 逻辑过期
3. 热点数据不过期
java
/**
* 缓存击穿解决方案
*/
public class CacheBreakdownSolution {
private final JedisPool jedisPool;
public CacheBreakdownSolution(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 方案1: 互斥锁
*/
public User getUserWithMutex(Long userId) {
String key = "user:" + userId;
String lockKey = "lock:user:" + userId;
try (Jedis jedis = jedisPool.getResource()) {
String value = jedis.get(key);
if (value != null) {
return parseUser(value);
}
// 尝试获取锁
String lockResult = jedis.set(lockKey, "1",
new SetParams().nx().ex(10));
if ("OK".equals(lockResult)) {
try {
// 双重检查
value = jedis.get(key);
if (value != null) {
return parseUser(value);
}
// 查询数据库
User user = getUserFromDB(userId);
if (user != null) {
jedis.setex(key, 3600, toJson(user));
}
return user;
} finally {
// 释放锁
jedis.del(lockKey);
}
} else {
// 等待重试
Thread.sleep(50);
return getUserWithMutex(userId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
/**
* 方案2: 逻辑过期
*/
public User getUserWithLogicalExpire(Long userId) {
String key = "user:" + userId;
try (Jedis jedis = jedisPool.getResource()) {
String value = jedis.get(key);
if (value == null) {
return null;
}
CacheData cacheData = parseCacheData(value);
// 检查是否过期
if (System.currentTimeMillis() < cacheData.expireTime) {
return cacheData.user;
}
// 已过期,尝试获取锁更新缓存
String lockKey = "lock:user:" + userId;
String lockResult = jedis.set(lockKey, "1",
new SetParams().nx().ex(10));
if ("OK".equals(lockResult)) {
// 异步更新缓存
CompletableFuture.runAsync(() -> {
try {
User user = getUserFromDB(userId);
if (user != null) {
CacheData newData = new CacheData();
newData.user = user;
newData.expireTime = System.currentTimeMillis()
+ 3600000;
jedis.set(key, toJson(newData));
}
} finally {
jedis.del(lockKey);
}
});
}
// 返回旧数据
return cacheData.user;
}
}
private User getUserFromDB(Long userId) {
return null;
}
private User parseUser(String json) {
return null;
}
private String toJson(Object obj) {
return "{}";
}
private CacheData parseCacheData(String json) {
return null;
}
static class CacheData {
User user;
long expireTime;
}
static class User {
private Long id;
private String name;
}
}
10.3 缓存雪崩
markdown
缓存雪崩
大量Key ──▶ 同时过期 ──▶ 数据库压力暴增
或
Redis宕机 ──▶ 所有请求打到数据库
解决方案:
1. 过期时间随机化
2. 集群高可用
3. 熔断降级
4. 多级缓存
java
/**
* 缓存雪崩解决方案
*/
public class CacheAvalancheSolution {
private final JedisPool jedisPool;
public CacheAvalancheSolution(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 方案1: 过期时间随机化
*/
public void setWithRandomExpire(String key, String value,
int baseSeconds) {
try (Jedis jedis = jedisPool.getResource()) {
// 基础过期时间 + 随机时间 (避免同时过期)
int randomSeconds = (int) (Math.random() * 300);
int expireSeconds = baseSeconds + randomSeconds;
jedis.setex(key, expireSeconds, value);
}
}
/**
* 方案2: 熔断降级
*/
public User getUserWithCircuitBreaker(Long userId) {
// 使用 Resilience4j 或 Sentinel 实现熔断
try {
return getUserFromCache(userId);
} catch (Exception e) {
// 熔断后返回默认值或降级逻辑
System.out.println("熔断降级: " + e.getMessage());
return getDefaultUser();
}
}
/**
* 方案3: 预热缓存
*/
public void warmUpCache(List<Long> hotUserIds) {
try (Jedis jedis = jedisPool.getResource()) {
Pipeline pipeline = jedis.pipelined();
for (Long userId : hotUserIds) {
User user = getUserFromDB(userId);
if (user != null) {
int expire = 3600 + (int) (Math.random() * 300);
pipeline.setex("user:" + userId, expire, toJson(user));
}
}
pipeline.sync();
System.out.println("缓存预热完成: " + hotUserIds.size());
}
}
private User getUserFromCache(Long userId) {
return null;
}
private User getUserFromDB(Long userId) {
return null;
}
private User getDefaultUser() {
return null;
}
private String toJson(User user) {
return "{}";
}
static class User {
private Long id;
private String name;
}
}
11. Spring Boot 集成
11.1 配置文件
yaml
# application.yml
spring:
redis:
# 单机模式
host: localhost
port: 6379
password:
database: 0
# 连接池配置
lettuce:
pool:
max-active: 100
max-idle: 20
min-idle: 5
max-wait: 10s
# 超时配置
timeout: 3s
connect-timeout: 5s
# 哨兵模式
# sentinel:
# master: mymaster
# nodes:
# - sentinel1:26379
# - sentinel2:26379
# - sentinel3:26379
# 集群模式
# cluster:
# nodes:
# - node1:6379
# - node2:6379
# - node3:6379
# max-redirects: 3
11.2 配置类
java
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
/**
* Redis 配置类
*/
@Configuration
@EnableCaching
public class RedisConfig {
/**
* 配置 RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Key 序列化
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value 序列化 (JSON)
Jackson2JsonRedisSerializer<Object> jsonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL,
JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
jsonSerializer.setObjectMapper(objectMapper);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
/**
* 配置缓存管理器
*/
@Bean
public RedisCacheManager cacheManager(
RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
// 针对不同缓存设置不同过期时间
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("users", defaultConfig.entryTtl(Duration.ofMinutes(30)));
configMap.put("products", defaultConfig.entryTtl(Duration.ofHours(2)));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(configMap)
.transactionAware()
.build();
}
}
11.3 使用示例
java
import org.springframework.cache.annotation.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* Redis 使用示例
*/
@Service
public class UserService {
private final RedisTemplate<String, Object> redisTemplate;
private final UserRepository userRepository;
public UserService(RedisTemplate<String, Object> redisTemplate,
UserRepository userRepository) {
this.redisTemplate = redisTemplate;
this.userRepository = userRepository;
}
/**
* 方式1: 使用注解缓存
*/
@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 clearAllUsers() {
System.out.println("清空所有用户缓存");
}
/**
* 方式2: 使用 RedisTemplate
*/
public User getUserWithTemplate(Long id) {
String key = "user:" + id;
// 查询缓存
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 查询数据库
user = userRepository.findById(id).orElse(null);
if (user != null) {
// 写入缓存
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
}
return user;
}
/**
* Hash 操作
*/
public void saveUserToHash(User user) {
String key = "user:hash:" + user.getId();
redisTemplate.opsForHash().put(key, "name", user.getName());
redisTemplate.opsForHash().put(key, "email", user.getEmail());
redisTemplate.expire(key, 1, TimeUnit.HOURS);
}
/**
* List 操作
*/
public void addToRecentViews(Long userId, Long productId) {
String key = "recent:views:" + userId;
redisTemplate.opsForList().leftPush(key, productId);
redisTemplate.opsForList().trim(key, 0, 99);
redisTemplate.expire(key, 7, TimeUnit.DAYS);
}
/**
* Set 操作
*/
public void addToFavorites(Long userId, Long productId) {
String key = "favorites:" + userId;
redisTemplate.opsForSet().add(key, productId);
}
/**
* ZSet 操作 (排行榜)
*/
public void updateLeaderboard(String userId, double score) {
redisTemplate.opsForZSet().add("leaderboard", userId, score);
}
public Set<Object> getTopUsers(int n) {
return redisTemplate.opsForZSet()
.reverseRange("leaderboard", 0, n - 1);
}
}
/**
* 用户实体
*/
class User {
private Long id;
private String name;
private String email;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
12. 监控与运维
12.1 监控指标
yaml
Redis 关键监控指标
内存指标:
├─ used_memory: 已用内存
├─ used_memory_rss: 物理内存
├─ mem_fragmentation_ratio: 内存碎片率
└─ maxmemory: 最大内存限制
性能指标:
├─ instantaneous_ops_per_sec: 每秒操作数
├─ hit_rate: 缓存命中率
├─ connected_clients: 连接数
└─ blocked_clients: 阻塞客户端数
持久化指标:
├─ rdb_last_bgsave_status: 最后RDB状态
├─ aof_last_rewrite_status: 最后AOF重写状态
└─ aof_current_size: 当前AOF大小
复制指标:
├─ role: 角色 (master/slave)
├─ connected_slaves: 从节点数
└─ master_repl_offset: 复制偏移量
告警阈值:
┌──────────────────────┬──────────────┐
│ Metric │ Threshold │
├──────────────────────┼──────────────┤
│ memory usage │ > 80% │
│ hit rate │ < 90% │
│ connected_clients │ > 5000 │
│ blocked_clients │ > 10 │
│ fragmentation_ratio │ > 1.5 │
└──────────────────────┴──────────────┘
12.2 监控代码
java
/**
* Redis 监控
*/
public class RedisMonitoring {
/**
* 获取 Redis 信息
*/
public static void printRedisInfo(Jedis jedis) {
// 获取所有信息
String info = jedis.info();
System.out.println(info);
// 获取特定部分
System.out.println("\n=== Memory ===");
System.out.println(jedis.info("memory"));
System.out.println("\n=== Stats ===");
System.out.println(jedis.info("stats"));
System.out.println("\n=== Clients ===");
System.out.println(jedis.info("clients"));
}
/**
* 获取慢查询日志
*/
public static void getSlowLog(Jedis jedis) {
List<Slowlog> slowlogs = jedis.slowlogGet(10);
System.out.println("慢查询日志:");
for (Slowlog log : slowlogs) {
System.out.printf("ID: %d, 耗时: %dμs, 命令: %s\n",
log.getId(),
log.getExecutionTime(),
log.getArgs()
);
}
}
/**
* 计算命中率
*/
public static void calculateHitRate(Jedis jedis) {
String info = jedis.info("stats");
// 解析 keyspace_hits 和 keyspace_misses
long hits = 0, misses = 0;
for (String line : info.split("\r\n")) {
if (line.startsWith("keyspace_hits:")) {
hits = Long.parseLong(line.split(":")[1]);
}
if (line.startsWith("keyspace_misses:")) {
misses = Long.parseLong(line.split(":")[1]);
}
}
if (hits + misses > 0) {
double hitRate = (double) hits / (hits + misses) * 100;
System.out.printf("缓存命中率: %.2f%%\n", hitRate);
}
}
/**
* 获取大 Key
*/
public static void findBigKeys(Jedis jedis) {
// 使用 SCAN 遍历所有 key
String cursor = "0";
int count = 0;
do {
ScanResult<String> result = jedis.scan(cursor,
new ScanParams().count(1000));
for (String key : result.getResult()) {
// 检查 key 大小
Long size = jedis.objectEncodingLength(key);
if (size != null && size > 10240) { // 10KB
String type = jedis.type(key);
System.out.printf("大Key: %s, 类型: %s, 大小: %d bytes\n",
key, type, size);
count++;
}
}
cursor = result.getCursor();
} while (!cursor.equals("0"));
System.out.println("共发现大Key: " + count);
}
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
printRedisInfo(jedis);
getSlowLog(jedis);
calculateHitRate(jedis);
// findBigKeys(jedis); // 生产环境谨慎使用
}
}
}
13. 总结
13.1 Redis 核心优势
markdown
Redis 核心优势
1. 性能 ★★★★★
- 基于内存,读写速度快
- 单线程避免锁竞争
- IO 多路复用
2. 数据结构 ★★★★★
- 丰富的数据类型
- 灵活满足各种场景
- 原子操作
3. 可用性 ★★★★★
- 主从复制
- 哨兵模式
- 集群模式
4. 持久化 ★★★★☆
- RDB 快照
- AOF 日志
- 混合持久化
5. 生态 ★★★★★
- 客户端丰富
- 社区活跃
- 文档完善
13.2 最佳实践总结
-
数据结构选择
- 简单键值用 String
- 对象数据用 Hash
- 队列用 List
- 去重用 Set
- 排序用 ZSet
-
Key 设计规范
- 使用统一前缀
- 避免过长 Key
- 设置合理过期时间
-
性能优化
- 使用连接池
- Pipeline 批量操作
- 避免大 Key
- 避免热点 Key
-
高可用部署
- 主从复制
- 哨兵监控
- 集群分片
- 定期备份
-
安全配置
- 设置密码
- 限制访问 IP
- 禁用危险命令
- 定期更新版本