Java Redis 完全教程:从入门到精通
目录
- 引言:什么是 Redis?
- 环境准备
- 安装并启动 Redis 服务器
- 安装 Redis 客户端(推荐)
- 配置 Java 开发环境
- Java Redis 客户端选择
- Jedis (经典,轻量级)
- Lettuce (推荐,高性能,异步支持)
- Spring Data Redis (Spring 生态首选)
- 第一个 Java Redis 程序 (使用 Jedis)
- Redis 数据类型与 Java 操作详解
- String
- Hash
- List
- Set
- Sorted Set (ZSet)
- Key 操作
- 高级应用
- 使用 Redis 实现缓存
缓存穿透、击穿、雪崩及解决方案
(图片来源网络,侵删) - 使用 Redis 实现分布式锁
- 简单实现与问题
- 使用 Redisson 实现健壮的分布式锁
- 使用 Redis 实现消息队列
- 使用 Redis 实现缓存
- 使用 Spring Boot 集成 Redis (最佳实践)
- 项目配置
- 使用
StringRedisTemplate或RedisTemplate - 自定义序列化方式
- 总结与最佳实践
引言:什么是 Redis?
Redis (Remote Dictionary Server) 是一个开源的、高性能的、基于内存的键值数据库,它通常被用作数据库、缓存和消息中间件。
核心特点:
- 速度快:数据存储在内存中,避免了磁盘 I/O,读写速度极快。
- 丰富的数据结构:支持 String、Hash、List、Set、Sorted Set 等多种数据类型。
- 原子性操作:所有操作都是原子性的,适合用于计数器、分布式锁等场景。
- 持久化:支持 RDB (快照) 和 AOF (日志) 两种持久化方式,保证数据安全。
- 多功能:除了缓存,还可以实现分布式锁、消息队列、排行榜等。
环境准备
a. 安装并启动 Redis 服务器
-
下载:从 Redis 官网 下载最新稳定版。
-
编译安装 (Linux/macOS):
(图片来源网络,侵删)# 解压 tar xzf redis-x.x.x.tar.gz cd redis-x.x.x # 编译 make # 启动 Redis 服务器 (默认端口 6379) src/redis-server
-
Windows: 可以使用 WSL (Windows Subsystem for Linux) 或下载预编译版本。
b. 安装 Redis 客户端
为了方便调试,强烈建议安装一个图形化客户端。
- RedisInsight: 官方出品的免费桌面客户端,功能强大,强烈推荐。
- AnotherRedisDesktopManager: 开源的跨平台客户端。
c. 配置 Java 开发环境
确保你已经安装了 JDK 8 或更高版本和 Maven。
Java Redis 客户端选择
在 Java 中操作 Redis,主要有以下几种客户端:

| 客户端 | 特点 | 适用场景 |
|---|---|---|
| Jedis | 最经典、最广为人知的客户端,API 直观,类似于 Redis 命令。 | 轻量级应用,对异步要求不高的项目。 |
| Lettuce | 基于 Netty,高性能,支持同步、异步和响应式编程,是 Spring Boot 2.x 的默认客户端。 | 新项目,特别是需要高性能或异步支持的项目。 |
| Spring Data Redis | Spring 生态的一部分,对 Redis 客户端(Jedis/Lettuce)进行了高级封装,提供了 RedisTemplate 等工具类,极大地简化了开发。 |
基于 Spring/Spring Boot 的项目,是 企业级应用的首选。 |
本教程将首先使用 Jedis 讲解基础操作,然后重点介绍 Spring Boot 集成 Redis 的最佳实践。
第一个 Java Redis 程序 (使用 Jedis)
-
创建 Maven 项目,并添加 Jedis 依赖:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.1</version> <!-- 使用较新版本 --> </dependency> -
编写 Java 代码:
import redis.clients.jedis.Jedis; import redis.clients.jedis.params.SetParams; public class JedisDemo { public static void main(String[] args) { // 1. 创建 Jedis 客户端,连接到 Redis 服务器 // 默认 host 是 "localhost", port 是 6379 Jedis jedis = new Jedis("localhost", 6379); // 2. 测试连接是否成功 String pingResponse = jedis.ping(); System.out.println("连接成功: " + pingResponse); // 输出: PONG // 3. 设置一个 String 类型的键值对 jedis.set("name", "张三"); System.out.println("设置 name: " + jedis.get("name")); // 输出: 张三 // 4. 设置一个有过期时间的键 (单位:秒) jedis.setex("temp_key", 10, "这是一个临时数据"); System.out.println("设置 temp_key (10秒后过期): " + jedis.get("temp_key")); // 5. 使用 SET 命令的参数 (NX: 不存在才设置, EX: 设置过期时间) SetParams params = SetParams.setParams().nx().ex(5); // 5秒内不存在才设置 String setResult = jedis.set("new_key", "new_value", params); System.out.println("NX/EX 设置结果: " + setResult); // 输出: OK 或 null // 6. 关闭连接 jedis.close(); } }运行此代码,如果一切正常,你将在控制台看到输出。
Redis 数据类型与 Java 操作详解
Jedis 客户端的方法名与 Redis 命令名基本一致,非常容易上手。
a. String
最基础的数据类型,可以存储文本、JSON、序列化对象等。
jedis.set("user:1001:name", "李四");
jedis.set("user:1001:age", "25");
// 批量设置
jedis.mset("key1", "value1", "key2", "value2");
// 获取
String name = jedis.get("user:1001:name");
// 数值原子递增
jedis.set("counter", "10");
jedis.incr("counter"); // 变为 11
jedis.incrBy("counter", 5); // 变为 16
// 数值原子递减
jedis.decr("counter"); // 变为 15
b. Hash
用于存储对象,一个键对应多个字段-值对,类似 Java 中的 Map<String, String>。
// 存储一个用户对象
jedis.hset("user:1001", "name", "王五");
jedis.hset("user:1001", "email", "wangwu@example.com");
jedis.hset("user:1001", "age", "30");
// 批量设置
Map<String, String> userMap = new HashMap<>();
userMap.put("name", "赵六");
userMap.put("email", "zhaoliu@example.com");
jedis.hmset("user:1002", userMap);
// 获取单个字段
String email = jedis.hget("user:1001", "email");
// 获取所有字段和值
Map<String, String> allFields = jedis.hgetAll("user:1001");
// 删除字段
jedis.hdel("user:1001", "age");
c. List
有序、可重复的列表,常作消息队列或文章列表。
// 从左侧推入
jedis.lpush("messages", "msg1", "msg2"); // 列表变为: [msg2, msg1]
// 从右侧推入
jedis.rpush("messages", "msg3"); // 列表变为: [msg2, msg1, msg3]
// 获取指定范围 (0到-1表示全部)
List<String> allMessages = jedis.lrange("messages", 0, -1); // [msg2, msg1, msg3]
// 获取长度
long len = jedis.llen("messages");
// 弹出元素 (从左侧)
String leftMessage = jedis.lpop("messages"); // 返回 "msg2"
d. Set
无序、不重复的集合,常用于标签、共同好友等。
// 添加元素
jedis.sadd("tags:java", "spring", "redis", "mysql");
jedis.sadd("tags:java", "spring"); // 重复添加,无效
// 获取所有成员
Set<String> tags = jedis.smembers("tags:java"); // 顺序不定
// 检查元素是否存在
boolean exists = jedis.sismember("tags:java", "redis");
// 计算数量
long count = jedis.scard("tags:java");
// 删除元素
jedis.srem("tags:java", "mysql");
e. Sorted Set (ZSet)
有序集合,每个元素都会关联一个 double 类型的分数,通过分数来排序。
// 添加元素,同时指定分数
jedis.zadd("leaderboard", 100, "userA");
jedis.zadd("leaderboard", 200, "userB");
jedis.zadd("leaderboard", 150, "userC");
// 按分数从小到大排序
Set<String> ascRanking = jedis.zrange("leaderboard", 0, -1); // [userA, userC, userB]
// 按分数从大到小排序
Set<String> descRanking = jedis.zrevrange("leaderboard", 0, -1); // [userB, userC, userA]
// 获取元素的分数
double scoreB = jedis.zscore("leaderboard", "userB");
// 获取元素的排名 (从0开始)
long rankA = jedis.zrank("leaderboard", "userA"); // 0
f. Key 操作
// 检查键是否存在
boolean exists = jedis.exists("name");
// 删除键
jedis.del("temp_key");
// 设置过期时间 (秒)
jedis.expire("name", 60);
// 获取键的剩余生存时间 (秒)
long ttl = jedis.ttl("name"); // 如果键不存在或已过期,返回 -2
// 获取键的数据类型
String type = jedis.type("name"); // 返回 "string", "hash", "list" 等
高级应用
使用 Redis 实现缓存
缓存是 Redis 最常见的用途,我们以查询用户信息为例。
// 模拟从数据库查询用户
public User getUserFromDB(long userId) {
System.out.println("正在从数据库查询用户 ID: " + userId);
// 模拟耗时
try { Thread.sleep(1000); } catch (InterruptedException e) {}
return new User(userId, "UserFromDB");
}
// 使用缓存的查询方法
public User getUserWithCache(long userId, Jedis jedis) {
String key = "user:" + userId;
// 1. 先从缓存查询
String cachedUserJson = jedis.get(key);
if (cachedUserJson != null) {
System.out.println("缓存命中!");
// 这里需要反序列化,实际项目中常用 JSON 库如 Jackson/Gson
return new User(userId, cachedUserJson); // 简化处理
}
// 2. 缓存未命中,从数据库查询
User user = getUserFromDB(userId);
// 3. 将查询结果存入缓存,并设置过期时间 (30 分钟)
// 这里需要序列化,简化处理
jedis.setex(key, 1800, user.getName());
return user;
}
缓存问题与解决方案:
- 缓存穿透:查询一个不存在的数据,请求直接打到数据库。
- 解决方案:如果查询结果为空,也将其缓存起来,并设置较短的过期时间(1 分钟)。
- 缓存击穿:一个热点 key 在某一刻突然失效,大量请求直接打到数据库。
- 解决方案:
- 互斥锁/分布式锁:只允许一个线程去查询数据库,其他线程等待。
- 热点数据永不过期:逻辑上设置过期时间,但后台异步刷新。
- 解决方案:
- 缓存雪崩:大量 key 在同一时间集体失效,导致请求全部打到数据库。
- 解决方案:给 key 的过期时间加上一个随机值,避免同时失效。
使用 Redis 实现分布式锁
在分布式系统中,为了保证多个服务实例对共享资源的互斥访问,需要分布式锁。
简单实现 (有问题版):
public boolean tryLock(String lockKey, String requestId, int expireTime) {
// set key value nx ex timeout
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
return "OK".equals(result);
}
public void releaseLock(String lockKey, String requestId) {
// 使用 Lua 脚本保证原子性
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else " +
"return 0 " +
"end";
jedis.eval(luaScript, 1, lockKey, requestId);
}
问题:jedis.set() 成功,但客户端在执行业务逻辑前崩溃了,锁永远不会被释放,导致死锁。
健壮的实现:使用 Redisson
Redisson 是一个强大的 Java Redis 客户端,它提供了开箱即用的分布式锁实现。
-
添加依赖:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.23.4</version> </dependency> -
使用代码:
import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class RedissonLockDemo { public static void main(String[] args) { Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("myLock"); try { // 1. 尝试获取锁,最多等待 10 秒,锁自动释放 30 秒 boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS); if (isLocked) { System.out.println("获取锁成功,执行业务逻辑..."); // 模拟业务处理 Thread.sleep(15000); } else { System.out.println("获取锁失败"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { // 2. 释放锁 if (lock.isHeldByCurrentThread()) { System.out.println("释放锁"); lock.unlock(); } } redisson.shutdown(); } }Redisson 的锁实现了 可重入、锁续期(看门狗机制)、公平锁/非公平锁 等复杂逻辑,非常健壮。
使用 Redis 实现消息队列
虽然 Redis 不是专业的消息队列,但可以使用 List 或 Stream 来实现简单的消息队列功能。
基于 List 的发布/订阅 (简单版):
- 生产者:
LPUSH或RPUSH将消息推入列表。 - 消费者:
RPOP或BLPOP(阻塞式弹出) 从列表中取出消息。
基于 Stream (功能更强大):
Redis 5.0 引入了 Stream 数据结构,提供了更专业的消息队列功能,如消费者组、消息确认等。
使用 Spring Boot 集成 Redis (最佳实践)
这是目前企业级开发中最主流的方式。
-
创建 Spring Boot 项目,并添加依赖:
<!-- Spring Data Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 使用 Lettuce 客户端,Spring Boot 2.x 默认 --> <!-- Spring Boot 会自动引入它 --> <!-- 为了方便操作 JSON,添加 Jackson 依赖 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> -
配置
application.yml:spring: redis: host: localhost port: 6379 # password: yourpassword # 如果有密码 database: 0 # 使用 0 号数据库 lettuce: pool: max-active: 8 # 连接池最大连接数 max-idle: 8 # 连接池最大空闲连接数 min-idle: 0 # 连接池最小空闲连接数 max-wait: -1ms # 连接池获取连接最大等待时间 -
使用
StringRedisTemplate
StringRedisTemplate 是 RedisTemplate 的一个特化版本,它的 key 和 value 都被强制为 String 类型,在大多数缓存场景下,我们推荐将对象序列化为 JSON 字符串进行存储,StringRedisTemplate 是最常用、最方便的选择。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public User getUserById(Long id) {
String key = "user:" + id;
// 1. 从缓存查询
String cachedUserJson = stringRedisTemplate.opsForValue().get(key);
if (cachedUserJson != null) {
System.out.println("缓存命中!");
// 使用 Jackson 反序列化
return JSON.parseObject(cachedUserJson, User.class);
}
// 2. 缓存未命中,从数据库查询 (模拟)
User user = getUserFromDB(id);
// 3. 存入缓存
if (user != null) {
// 使用 Jackson 序列化
String userJson = JSON.toJSONString(user);
stringRedisTemplate.opsForValue().set(key, userJson, 30, TimeUnit.MINUTES);
}
return user;
}
// ... 其他方法
}
自定义序列化方式 (可选)
默认情况下,RedisTemplate 使用的是 JdkSerializationRedisSerializer,它会产生二进制数据,不便于阅读,我们可以将其替换为 Jackson2JsonRedisSerializer。
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 1. 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 2. 创建 JSON 序列化器配置
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(om);
// 3. 设置 key 和 hashKey 的序列化器
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 4. 设置 value 和 hashValue 的序列化器
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
// 5. 初始化 RedisTemplate
template.afterPropertiesSet();
return template;
}
}
配置完成后,你就可以直接在 RedisTemplate 中存取 Java 对象了,它会自动完成 JSON 的序列化和反序列化。
总结与最佳实践
- 选择合适的客户端:新项目优先选择 Lettuce,Spring 项目优先选择 Spring Data Redis。
- 优先使用
StringRedisTemplate:在缓存场景下,将对象序列化为 JSON 字符串存储是最佳实践,简单、高效、可读性好。 - 自定义序列化器:如果必须使用
RedisTemplate,请务必将默认的 JDK 序列化器替换为 JSON 序列化器 (Jackson2JsonRedisSerializer)。 - 为所有缓存数据设置过期时间:这是防止缓存雪崩和避免数据不一致的基本要求。
- 善用高级数据结构:不要只用 String,Hash、List、ZSet 等能解决很多实际问题。
- 复杂功能使用成熟库:对于分布式锁、消息队列等复杂场景,直接使用 Redisson 等成熟库,避免重复造轮子和踩坑。
- 注意连接池配置:在生产环境中,务必配置好 Redis 客户端的连接池参数,以获得最佳性能和稳定性。
希望这份详尽的教程能帮助你从零开始掌握 Java Redis 开发!
