在高并发场景中,传统数据库往往面临读写压力过大的问题。Redis(Remote Dictionary Server)作为一款高性能的内存数据库,凭借其快读写、多数据结构、支持持久化等特性,成为缓存、分布式锁、计数器等场景的首选工具。本文将从 Redis 核心概念出发,结合 Java 实战,带你快速掌握 Redis 的入门用法。
一、Redis 核心概念
在使用 Redis 前,需先理解其核心特性和数据模型,这是后续实战的基础。
1. 核心特性
- 内存存储:数据主要存于内存,读写速度极快(单机 QPS 可达 10 万+)。
- 多数据结构:支持 String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Sorted Set(有序集合)等多种结构。
- 持久化:支持 RDB(快照)和 AOF(日志)两种持久化方式,避免内存数据丢失。
- 高可用:支持主从复制、哨兵模式、集群模式,保证服务稳定。
- 原子操作:提供incr、decr等原子命令,适合实现计数器、分布式锁等场景。
2. 核心数据结构
Redis 的核心价值在于丰富的数据结构,不同结构对应不同场景,新手需重点掌握以下 5 种:
| 数据结构 | 特点 | 适用场景 |
|---|---|---|
| String(字符串) | 最基础类型,可存储文本、数字,支持拼接、截取 | 缓存用户信息、存储计数器、分布式 ID |
| Hash(哈希) | 键值对集合,适合存储对象(如用户信息) | 缓存用户详情、商品信息 |
| List(列表) | 有序、可重复的元素集合,支持两端操作 | 实现消息队列、最新消息列表 |
| Set(集合) | 无序、不可重复的元素集合,支持交集、并集 | 好友关系、标签去重、共同关注 |
| Sorted Set(有序集合) | 有序、不可重复,元素关联“分数”排序 | 排行榜、带权重的消息队列 |
二、环境准备
1. 安装 Redis
推荐使用 Docker 快速部署(本地安装需手动配置,步骤较繁琐):
# 拉取 Redis 镜像(6.2 版本为例,稳定且常用)
docker pull redis:6.2
# 启动容器(映射 6379 端口,设置密码为 123456)
docker run -d --name redis -p 6379:6379 redis:6.2 --requirepass "123456"启动后,可通过 redis-cli 测试连接(需先进入容器):
# 进入 Redis 容器
docker exec -it redis /bin/bash
# 连接 Redis 服务(输入密码 123456)
redis-cli -a 123456
# 测试命令(返回 PONG 说明连接成功)
ping2. 引入 Java 客户端依赖
Java 操作 Redis 最常用的客户端是 Jedis(轻量、易用)和 Spring Data Redis(集成 Spring,简化开发)。本文先以 Jedis 为例,后续补充 Spring 集成方案。
在 Maven 项目的 pom.xml 中添加 Jedis 依赖:
<!-- Jedis 客户端依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!-- 测试依赖(可选,用于编写测试用例) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>3. 初始化 Jedis 连接
创建 RedisClient 工具类,封装 Jedis 连接的初始化和关闭(实际项目中建议用连接池,避免频繁创建连接):
import redis.clients.jedis.Jedis;
public class RedisClient {
// Redis 服务地址和端口
private static final String HOST = "localhost";
private static final int PORT = 6379;
// Redis 密码
private static final String PASSWORD = "123456";
// 获取 Jedis 连接
public static Jedis getConnection() {
// 1. 创建连接
Jedis jedis = new Jedis(HOST, PORT);
// 2. 验证密码
jedis.auth(PASSWORD);
// 3. 选择数据库(默认 0 号库,共 16 个库,0-15)
jedis.select(0);
return jedis;
}
// 关闭连接
public static void closeConnection(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
}注意:实际项目中需使用 Jedis 连接池(JedisPool),避免频繁创建/关闭连接导致性能损耗,下文会补充连接池配置。三、Java 操作 Redis 实战
以“电商缓存”场景为例,演示 5 种核心数据结构的常用操作。
1. String 类型:缓存商品名称与计数器
String 是 Redis 最基础的类型,可存储文本或数字,支持 set(存值)、get(取值)、incr(自增)等命令。
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
public class RedisStringTest {
@Test
public void testStringOps() {
// 1. 获取连接
Jedis jedis = RedisClient.getConnection();
try {
// (1)存储商品名称(key:product:name:1,value:华为 Mate 60)
jedis.set("product:name:1", "华为 Mate 60");
System.out.println("商品名称:" + jedis.get("product:name:1"));
// (2)存储商品库存(数字类型,支持自增自减)
jedis.set("product:stock:1", "100"); // 初始库存 100
jedis.incr("product:stock:1"); // 库存 +1(变为 101)
jedis.decrBy("product:stock:1", 5); // 库存 -5(变为 96)
System.out.println("商品库存:" + jedis.get("product:stock:1"));
// (3)设置过期时间(10 秒后自动删除,适合临时缓存)
jedis.setex("product:temp:1", 10, "临时缓存数据");
System.out.println("临时缓存剩余时间(秒):" + jedis.ttl("product:temp:1"));
} finally {
// 2. 关闭连接(避免资源泄漏)
RedisClient.closeConnection(jedis);
}
}
}2. Hash 类型:缓存用户详情
Hash 适合存储“对象”(如用户、商品),可单独操作某个字段(无需修改整个对象),节省内存和带宽。
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import java.util.Map;
public class RedisHashTest {
@Test
public void testHashOps() {
Jedis jedis = RedisClient.getConnection();
try {
// (1)存储用户详情(key:user:1,字段:name/age/phone)
jedis.hset("user:1", "name", "张三");
jedis.hset("user:1", "age", "25");
jedis.hset("user:1", "phone", "13800138000");
// (2)获取单个字段(用户姓名)
System.out.println("用户姓名:" + jedis.hget("user:1", "name"));
// (3)获取所有字段和值(完整用户信息)
Map<String, String> userMap = jedis.hgetAll("user:1");
System.out.println("完整用户信息:" + userMap);
// (4)删除某个字段(删除用户手机号)
jedis.hdel("user:1", "phone");
System.out.println("删除手机号后:" + jedis.hgetAll("user:1"));
} finally {
RedisClient.closeConnection(jedis);
}
}
}3. List 类型:实现简单消息队列
List 是有序、可重复的集合,支持 lpush(左插)、rpop(右取)等命令,适合实现“先进先出”(FIFO)的消息队列。
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
public class RedisListTest {
@Test
public void testListOps() {
Jedis jedis = RedisClient.getConnection();
try {
// 队列 key:order:queue(存储订单 ID)
String queueKey = "order:queue";
// (1)生产者:向左插入 3 个订单 ID(模拟下单)
jedis.lpush(queueKey, "order1001", "order1002", "order1003");
System.out.println("队列长度:" + jedis.llen(queueKey)); // 输出 3
// (2)消费者:向右取出订单 ID(模拟处理订单)
String orderId1 = jedis.rpop(queueKey);
String orderId2 = jedis.rpop(queueKey);
System.out.println("处理的订单:" + orderId1 + "、" + orderId2);
System.out.println("剩余队列长度:" + jedis.llen(queueKey)); // 输出 1
// (3)查看队列所有元素
System.out.println("剩余订单:" + jedis.lrange(queueKey, 0, -1));
} finally {
RedisClient.closeConnection(jedis);
}
}
}4. Set 类型:实现用户标签去重
Set 是无序、不可重复的集合,支持交集、并集、差集等操作,适合标签去重、好友关系管理。
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import java.util.Set;
public class RedisSetTest {
@Test
public void testSetOps() {
Jedis jedis = RedisClient.getConnection();
try {
// (1)存储用户 1 的标签(key:user:tags:1)
jedis.sadd("user:tags:1", "足球", "篮球", "游戏");
// 存储用户 2 的标签(key:user:tags:2)
jedis.sadd("user:tags:2", "篮球", "音乐", "电影");
// (2)获取用户 1 的所有标签(自动去重)
Set<String> user1Tags = jedis.smembers("user:tags:1");
System.out.println("用户 1 标签:" + user1Tags);
// (3)判断用户 1 是否有“游戏”标签
boolean hasGameTag = jedis.sismember("user:tags:1", "游戏");
System.out.println("用户 1 有游戏标签:" + hasGameTag); // 输出 true
// (4)求两个用户的共同标签(交集)
Set<String> commonTags = jedis.sinter("user:tags:1", "user:tags:2");
System.out.println("共同标签:" + commonTags); // 输出 [篮球]
} finally {
RedisClient.closeConnection(jedis);
}
}
}5. Sorted Set 类型:实现商品销量排行榜
Sorted Set 与 Set 类似,但每个元素关联一个“分数”(score),按分数自动排序,适合实现排行榜、带权重的队列。
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;
public class RedisSortedSetTest {
@Test
public void testSortedSetOps() {
Jedis jedis = RedisClient.getConnection();
try {
// 排行榜 key:product:sales:rank(按销量排序)
String rankKey = "product:sales:rank";
// (1)存储商品销量(商品 ID 为元素,销量为分数)
jedis.zadd(rankKey, 1000, "product1001"); // 商品 1 销量 1000
jedis.zadd(rankKey, 2500, "product1002"); // 商品 2 销量 2500
jedis.zadd(rankKey, 1800, "product1003"); // 商品 3 销量 1800
// (2)获取销量前 2 的商品(降序排列,0 表示第 1 名,1 表示第 2 名)
Set<Tuple> top2 = jedis.zrevrangeWithScores(rankKey, 0, 1);
System.out.println("销量前 2 商品:");
for (Tuple tuple : top2) {
System.out.println("商品 ID:" + tuple.getElement() + ",销量:" + tuple.getScore());
}
// (3)获取商品 3 的销量排名(降序,返回排名索引,从 0 开始)
long rank = jedis.zrevrank(rankKey, "product1003");
System.out.println("商品 3 销量排名:第 " + (rank + 1) + " 名"); // 输出第 2 名
} finally {
RedisClient.closeConnection(jedis);
}
}
}四、进阶:Jedis 连接池配置
前文的简单连接方式不适合高并发场景,需使用 JedisPool(连接池)管理连接,减少连接创建/关闭的开销。
1. 配置连接池
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisPoolClient {
// 连接池配置
private static final JedisPoolConfig POOL_CONFIG;
// 连接池实例(单例)
private static final JedisPool JEDIS_POOL;
// 静态代码块:初始化连接池
static {
// 1. 配置连接池参数
POOL_CONFIG = new JedisPoolConfig();
POOL_CONFIG.setMaxTotal(20); // 最大连接数
POOL_CONFIG.setMaxIdle(10); // 最大空闲连接数
POOL_CONFIG.setMinIdle(5); // 最小空闲连接数
POOL_CONFIG.setTestOnBorrow(true); // 借连接时测试可用性
// 2. 初始化连接池
JEDIS_POOL = new JedisPool(
POOL_CONFIG,
"localhost", // 地址
6379, // 端口
10000, // 连接超时时间(毫秒)
"123456" // 密码
);
}
// 从连接池获取连接
public static Jedis getConnection() {
return JEDIS_POOL.getResource();
}
// 关闭连接(实际是归还到连接池)
public static void closeConnection(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
}2. 使用连接池
后续操作只需替换 RedisClient 为 RedisPoolClient 即可,用法完全一致:
// 从连接池获取连接
Jedis jedis = RedisPoolClient.getConnection();
try {
// 执行 Redis 命令(如 set、get 等)
} finally {
// 归还连接到池
RedisPoolClient.closeConnection(jedis);
}五、入门必备:常见问题与最佳实践
1. 键名设计规范
- 使用冒号分隔层级:如
product:name:1(业务:字段:ID),便于区分和管理。 - 避免过长键名:键名会占用内存,建议简洁(如用
user:1而非user_info_123)。 - 统一命名风格:全小写,避免特殊字符,保持团队一致。
2. 避免数据丢失:持久化配置
Redis 内存数据默认不持久化,需手动开启 RDB 或 AOF:
- RDB:定期生成内存快照(适合数据恢复,但可能丢失少量数据)。
- AOF:记录每一条写命令(数据更安全,但文件