1. Redis 入门
1.1 Redis 简介:核心定位与特性
Redis(Remote Dictionary Server)是一款高性能的内存数据库,它以键值(Key-Value)结构存储数据。由于其所有数据主要存储在内存中,Redis 提供了极高的数据读写速度和吞吐量。在互联网技术领域,Redis 因其卓越的性能和灵活的数据结构支持,已成为应用最为广泛的存储中间件之一。
- 内存数据库: 数据主要驻留在内存中,确保了快速的数据访问响应时间。
- 键值结构: 数据以唯一的键(Key)和对应的值(Value)的形式进行存取,类似于哈希表,简单高效。
- 存储中间件: Redis 通常作为应用程序和传统持久化数据库(如 MySQL)之间的缓冲层或辅助存储。它常用于实现高性能缓存、会话管理、消息队列、分布式锁、实时统计等多种场景。
1.2 Redis 与传统数据库的定位差异(背景知识补充)
你可能会想,既然有了像 MySQL 这样的关系型数据库,为什么还需要 Redis 呢?它们之间是互补关系,而非替代关系。
- MySQL(关系型数据库):
- 数据存储:主要基于磁盘,数据持久化能力强。
- 数据结构:表格化、结构化数据,支持复杂的 SQL 查询(Join、事务等)。
- 适用场景:适合存储大量需要永久保存的结构化数据,如核心业务数据(订单、用户信息),强调数据一致性和事务完整性。
- Redis(非关系型数据库):
- 数据存储:主要基于内存,读写速度极快。
- 数据结构:提供多种丰富的非结构化数据类型(String, Hash, List, Set, Sorted Set)。
- 适用场景:适用于高并发、低延迟的读写操作,如数据缓存、排行榜、计数器、会话管理等,旨在提升系统性能和用户体验。
在“苍穹外卖”项目中,Redis 通常会被用作缓存来加速数据访问,例如存储热门菜品、商家信息等,避免每次请求都去查询速度相对较慢的 MySQL 数据库。
2. Redis 核心数据类型介绍
Redis 存储的是 Key-Value 结构的数据,其中 Key 始终是字符串类型。而 Value 则可以支持五种常用的数据类型,每种类型都有其独特的结构和适用场景:
- 字符串 (String): 最基本的数据类型,可以存储文本、数字或二进制数据。
- 哈希 (Hash): 存储一个键值对集合,非常适合存储对象(如用户或商品信息)。
- 列表 (List): 一个有序的字符串元素集合,可以从两端进行插入和删除操作,常用于实现队列或栈。
- 集合 (Set): 一个无序的、不重复的字符串元素集合,支持集合间的交集、并集、差集等操作。
- 有序集合 (Sorted Set / ZSet): 类似于集合,但每个成员都会关联一个
double
类型的分数,Redis 会根据分数对成员进行排序,常用于排行榜等场景。
3. Redis 常用命令
掌握 Redis 命令是直接与 Redis 服务交互的基础。以下列出并解释了各种数据类型的常用操作命令:
3.1 字符串操作命令
用于操作 String 类型的数据:
SET
key
value
: 设置指定 Key 的值。GET
key
: 获取指定 Key 的值。SETEX
key
seconds
value
: 设置指定 Key 的值,并为其设置一个过期时间(单位:秒)。SETNX
key
value
: 仅当 Key 不存在时,设置 Key 的值。此命令常用于实现分布式锁。
3.2 哈希操作命令
用于操作 Hash 类型的数据,常用于存储对象:
HSET
key
field
value
: 将哈希表key
中字段field
的值设为value
。HGET
key
field
: 获取存储在哈希表中指定字段的值。HDEL
key
field
: 删除哈希表中指定字段。HKEYS
key
: 获取哈希表中所有字段。HVALS
key
: 获取哈希表中所有值。
3.3 列表操作命令
用于操作 List 类型的数据,其元素按插入顺序排序:
LPUSH
key
value1
[value2
]: 将一个或多个值插入到列表头部(左侧)。LRANGE
key
start
stop
: 获取列表指定索引范围内的元素。RPOP
key
: 移除并获取列表的最后一个元素(右侧)。LLEN
key
: 获取列表的长度。BRPOP
key1
[key2
]timeout
: 移出并获取列表的最后一个元素。如果列表为空,则阻塞直到有元素可弹出或达到超时时间。此命令在构建消息队列时非常有用。
3.4 集合操作命令
用于操作 Set 类型的数据,集合成员唯一且无序:
SADD
key
member1
[member2
]: 向集合添加一个或多个成员。SMEMBERS
key
: 返回集合中的所有成员。SCARD
key
: 获取集合的成员数量。SINTER
key1
[key2
]: 返回给定所有集合的交集。SUNION
key1
[key2
]: 返回所有给定集合的并集。SREM
key
member1
[member2
]: 移除集合中一个或多个成员。
3.5 有序集合操作命令
用于操作 Sorted Set 类型的数据,其成员通过分数进行排序:
ZADD
key
score1
member1
[score2
member2
]: 向有序集合添加一个或多个成员,并指定分数。ZRANGE
key
start
stop
[WITHSCORES
]: 通过索引区间返回有序集合中指定区间内的成员。WITHSCORES
可同时返回分数。ZINCRBY
key
increment
member
: 有序集合中对指定成员的分数增加增量increment
。ZREM
key
member
[member ...
]: 移除有序集合中的一个或多个成员。
3.6 通用命令
这些命令适用于所有数据类型的 Key:
KEYS pattern
: 查找所有符合给定模式 (pattern
) 的 Key。警告:在生产环境中,此命令应谨慎使用。当数据量巨大时,它可能导致 Redis 服务阻塞,严重影响性能。EXISTS
key
: 检查给定 Key 是否存在。TYPE
key
: 返回 Key 所存储的值的类型(如string
、hash
、list
、set
、zset
)。DEL
key
: 删除 Key。
4. 在 Java 中操作 Redis
4.1 Redis 的 Java 客户端:实现 Java 应用与 Redis 的交互
我们已经学习了 Redis 的各种命令,它们是与 Redis 服务直接交互的方式。在 Java 应用程序中,为了能够发送这些命令并接收响应,我们需要使用 Redis 的 Java 客户端库,这就像我们通过 JDBC 驱动来操作 MySQL 数据库一样。
目前主流的 Redis Java 客户端包括:
- Jedis: 这是一个功能全面且成熟的客户端,其 API 设计与 Redis 命令高度对应,使用直接且易于理解。
- Lettuce: 相对较新的客户端,基于 Netty 框架实现,支持异步、响应式编程模型,在高并发场景下性能表现出色。
4.2 Spring Data Redis:Spring Boot 项目的优选方案
在“苍穹外卖”这类基于 Spring Boot 的项目中,我们通常会选择使用 Spring Data Redis。Spring Data Redis 是 Spring Framework 对各种 Redis 客户端(如 Jedis 和 Lettuce)的高度抽象和集成,它提供了一套更符合 Spring 编程习惯的 API,极大地简化了 Redis 的操作。
通过引入以下 Maven 依赖,spring-boot-starter-data-redis
会自动配置 Spring Boot 项目与 Redis 的连接,并集成默认的客户端(通常是 Lettuce 或 Jedis,具体取决于 Spring Boot 版本及配置):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
这个 Starter 依赖为我们带来了什么?
- 自动配置: 无需手动编写复杂的连接池、序列化器等配置,Spring Boot 会根据
application.properties
或application.yml
中的配置(如spring.redis.host
,spring.redis.port
等)自动创建并管理 Redis 连接工厂和 RedisTemplate 等核心组件。这在“苍穹外卖”项目中意味着你只需几行配置就能让项目具备 Redis 连接能力。 - Spring 风格的 API: Spring Data Redis 提供了
RedisTemplate
和StringRedisTemplate
等核心类,通过它们你可以用更面向对象的方式来操作 Redis 的五种数据类型,而不需要直接记忆和拼接 Redis 命令。例如,你可以直接调用redisTemplate.opsForValue().set("key", "value")
来设置字符串,这比使用原生客户端更加便捷。 - 统一的异常处理: 将底层客户端的异常统一转换为 Spring 框架的 DataAccessExceptions,便于错误处理。
- 高度封装: 隐藏了底层客户端(Jedis/Lettuce)的具体实现细节,使得切换底层客户端变得轻而易举,无需修改业务代码。
- 数据序列化: 内置多种序列化器(如 Jackson2JsonRedisSerializer、JdkSerializationRedisSerializer),可以方便地将 Java 对象序列化为字节数组存储到 Redis,或从 Redis 读取后反序列化为 Java 对象。在“苍穹外卖”中,这对于缓存复杂的 Java 对象(如菜品列表、用户登录信息)至关重要。
好的,非常抱歉,看来我之前补充的细节还不够深入,没有完全满足你的期望。你提得非常对,RedisTemplate
作为 Spring Data Redis
的核心组件,是我们在 Java 代码中操作 Redis 的真正“主力”,确实需要重点讲解!
这次我一定会更详细地阐述 RedisTemplate
,并结合“苍穹外卖”项目的实际应用场景进行说明。
4. 在 Java 中操作 Redis
4.1 Redis 的 Java 客户端:实现 Java 应用与 Redis 的交互
(这部分保持不变,确保衔接流畅)
我们已经学习了 Redis 的各种命令,它们是与 Redis 服务直接交互的方式。在 Java 应用程序中,为了能够发送这些命令并接收响应,我们需要使用 Redis 的 Java 客户端库,这就像我们通过 JDBC 驱动来操作 MySQL 数据库一样。
目前主流的 Redis Java 客户端包括:
- Jedis: 这是一个功能全面且成熟的客户端,其 API 设计与 Redis 命令高度对应,使用直接且易于理解。
- Lettuce: 相对较新的客户端,基于 Netty 框架实现,支持异步、响应式编程模型,在高并发场景下性能表现出色。
4.2 Spring Data Redis:Spring Boot 项目的优选方案
在“苍穹外卖”这类基于 Spring Boot 的项目中,我们通常会选择使用 Spring Data Redis。Spring Data Redis 是 Spring Framework 对各种 Redis 客户端(如 Jedis 和 Lettuce)的高度抽象和集成,它提供了一套更符合 Spring 编程习惯的 API,极大地简化了 Redis 的操作。
通过引入以下 Maven 依赖,spring-boot-starter-data-redis
会自动配置 Spring Boot 项目与 Redis 的连接,并集成默认的客户端(通常是 Lettuce 或 Jedis,具体取决于 Spring Boot 版本及配置):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
这个 Starter 依赖为我们带来了什么?
- 自动配置: 无需手动编写复杂的连接池、序列化器等配置,Spring Boot 会根据
application.properties
或application.yml
中的配置(如spring.redis.host
,spring.redis.port
等)自动创建并管理 Redis 连接工厂和RedisTemplate
等核心组件。 - Spring 风格的 API: 提供了一套更面向对象、更易于理解和使用的 API。
- 统一的异常处理: 将底层客户端的异常统一转换为 Spring 框架的 DataAccessExceptions,便于错误处理。
- 高度封装: 隐藏了底层客户端(Jedis/Lettuce)的具体实现细节,使得切换底层客户端变得轻而易举。
- 数据序列化: 内置多种序列化器,用于在 Java 对象和 Redis 存储格式之间进行转换。
4.3 深入理解 RedisTemplate:操作 Redis 的“瑞士军刀”
现在,我们来重点聊聊 RedisTemplate
。它是 Spring Data Redis
提供的核心工具类,就如同 JDBC 中的 JdbcTemplate
一样,它为我们提供了一套高度抽象的、方便易用的接口来操作 Redis 的各种数据类型和命令。
RedisTemplate
的核心作用:
- 封装底层操作: 你无需直接调用 Jedis 或 Lettuce 的客户端 API,
RedisTemplate
帮你封装了底层的连接管理、命令执行等细节。 - 类型安全操作: 它提供了对 Redis 五种核心数据类型(String, Hash, List, Set, ZSet)的专属操作接口,让你能以更符合 Java 习惯的方式来操作数据。
- 数据序列化/反序列化: 这是
RedisTemplate
最重要的特性之一。由于 Redis 存储的是字节序列,而我们在 Java 中操作的是对象,RedisTemplate
会自动处理 Java 对象与 Redis 存储格式之间的序列化和反序列化,极大地简化了数据存取过程。
4.3.1 RedisTemplate
的常用操作接口(Ops)
RedisTemplate
本身并不直接提供 set
、get
等方法,而是通过一系列 opsForXxx()
方法获取对应数据类型的操作接口(Operations Interface)。这样设计,使得每种数据类型的操作都非常清晰和专业。
-
opsForValue()
:操作 String(字符串)类型数据- 作用: 用于对 Redis 中的 String 类型 Key-Value 进行操作,包括设置、获取、递增/递减等。
- “苍穹外卖”应用场景:
- 缓存简单的配置信息: 比如某个开关状态、每日特价的商品 ID 等。
- 存储验证码:
redisTemplate.opsForValue().set("sms_code:" + phone, code, 5, TimeUnit.MINUTES);
(设置带过期时间的验证码) - 缓存热门菜品的基本信息(如果菜品对象比较简单,或只缓存ID):
redisTemplate.opsForValue().set("dish:1001:name", "招牌烤鸭");
-
opsForHash()
:操作 Hash(哈希)类型数据- 作用: 用于对 Redis 中的 Hash 类型数据进行操作,每个 Hash Key 内部可以存储多个 Field-Value 对。
- “苍穹外卖”应用场景:
- 缓存用户信息:
redisTemplate.opsForHash().put("user:100", "name", "张三"); redisTemplate.opsForHash().put("user:100", "age", 25);
- 缓存菜品或套餐的详细信息: 将一个
Dish
或Setmeal
对象的所有属性(如名称、价格、图片、描述等)作为 Field-Value 存储在一个 Hash Key 下,方便一次性获取或部分更新。// 假设有一个 Dish 对象 dish // redisTemplate.opsForHash().putAll("dish:" + dish.getId(), BeanUtil.beanToMap(dish)); // 使用hutool工具类将bean转为map
- 缓存用户信息:
-
opsForList()
:操作 List(列表)类型数据- 作用: 用于对 Redis 中的 List 类型数据进行操作,实现队列、栈等功能。
- “苍穹外卖”应用场景:
- 消息队列: 用户下单后,将订单 ID 放入一个 List 作为消息队列,后台消费者从 List 中取出处理。
- 最新动态列表: 存储用户最近浏览的商品、最新发布的评论等。
-
opsForSet()
:操作 Set(集合)类型数据- 作用: 用于对 Redis 中的 Set 类型数据进行操作,集合元素唯一且无序。
- “苍穹外卖”应用场景:
- 用户标签:
redisTemplate.opsForSet().add("user:101:tags", "吃货", "夜宵党");
- 抽奖活动参与者: 记录所有参与抽奖的用户 ID,利用 Set 的唯一性避免重复参与。
- 共同好友/关注: 计算两个用户共同关注的商家或菜品(交集)。
- 用户标签:
-
opsForZSet()
:操作 Sorted Set(有序集合)类型数据- 作用: 用于对 Redis 中的 Sorted Set 类型数据进行操作,元素唯一且带分数,按分数排序。
- “苍穹外卖”应用场景:
- 菜品/商家排行榜: 根据销售额、用户评分等作为分数,实现热门菜品榜、受欢迎商家榜。
- 用户积分榜: 记录用户的积分,并实时更新和排序。
4.3.2 关键:数据序列化策略
在使用 RedisTemplate
缓存 Java 对象时,序列化是理解和配置的重中之重。因为 Redis 内部只存储字节序列,而 RedisTemplate
会自动处理 Java 对象与这些字节序列的转换。
Spring Data Redis 提供了多种序列化器,你需要根据实际需求选择:
-
JdkSerializationRedisSerializer
(默认配置时可能使用):- 特点: 使用 Java 对象的默认序列化机制。被序列化的对象需要实现
Serializable
接口。 - 优点: 使用简单,对 Java 对象支持度好。
- 缺点: 序列化后的数据可读性差(二进制乱码),占用空间相对较大,存在跨语言兼容性问题。在“苍穹外卖”项目中,如果直接在 Redis 中查看缓存数据,会很难识别。
- 特点: 使用 Java 对象的默认序列化机制。被序列化的对象需要实现
-
StringRedisSerializer
:- 特点: 专门用于 Key 和 Value 都是字符串的情况。
- 优点: 可读性好,性能高。
- 缺点: 只能处理 String 类型。
-
Jackson2JsonRedisSerializer
或GenericJackson2JsonRedisSerializer
:- 特点: 将 Java 对象序列化为 JSON 字符串。
- 优点: 可读性强(JSON 格式),跨语言兼容性好,占用空间相对较小。
- 缺点: 需要引入 Jackson 库。
- 在“苍穹外卖”中的重要性: 强烈推荐使用!因为“苍穹外卖”通常需要缓存复杂的 Java 对象(如
Category
、Dish
、SetmealVO
),将它们序列化为 JSON 存储在 Redis 中,不仅方便调试(直接看 Redis 里是 JSON 字符串),也方便前端或其他服务获取数据。
配置示例(application.yml
或 Java 配置类):
为了让 RedisTemplate
使用 JSON 序列化器,你通常需要在 Spring Boot 的配置类中进行如下配置(或通过 application.yml
配置):
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field, get 和 set, 以及修饰符范围,ANY 是都有包括 private 和 public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非 final 修饰的,final 类在反序列化时会报错
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // 如果Hash的Value也是对象
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
通过上述配置,你的 RedisTemplate
在存取 Java 对象时,就会自动将其转换为可读的 JSON 格式。
4.3.3 RedisTemplate
在“苍穹外卖”项目中的实际应用
在“苍穹外卖”项目中,RedisTemplate
将无处不在,尤其是在需要提升性能和用户体验的场景:
-
缓存热门分类/菜品:
- 当用户访问首页或菜品列表时,频繁查询数据库会造成压力。
- 可以将菜品分类(
Category
)或热门菜品(Dish
)等数据缓存到 Redis 中。 - 例如:在
CategoryService
或DishService
中注入RedisTemplate
,首次查询数据库后,将数据存储到 Redis,后续请求直接从 Redis 获取。
@Service public class CategoryServiceImpl implements CategoryService { @Autowired private CategoryMapper categoryMapper; @Autowired private RedisTemplate redisTemplate; // 自动注入配置好的 RedisTemplate @Override public List<Category> list(Integer type) { // 查询 Redis 缓存 String key = "category_list:" + type; List<Category> categoryList = (List<Category>) redisTemplate.opsForValue().get(key); if (categoryList != null && !categoryList.isEmpty()) { return categoryList; // 缓存命中,直接返回 } // 缓存未命中,查询数据库 categoryList = categoryMapper.list(type); // 将数据存入 Redis 缓存(使用 JSON 序列化) if (categoryList != null && !categoryList.isEmpty()) { redisTemplate.opsForValue().set(key, categoryList, 1, TimeUnit.HOURS); // 缓存1小时 } return categoryList; } }
-
管理用户登录 Token/Session:
- 用户登录成功后,生成的 JWT Token 或会话信息可以存储在 Redis 中,并设置过期时间。
- 每次请求校验 Token 时,直接从 Redis 获取,避免访问数据库。
-
统计访问量/点赞数:
- 使用 Redis 的原子操作(如
incr
)来快速更新菜品或商家的点赞数、浏览量。 - 使用 Sorted Set 来维护菜品或商家的销售排行榜。
- 使用 Redis 的原子操作(如
-
短信验证码存储:
- 将用户手机号和验证码作为 Key-Value 存储,并设置短过期时间(如 5 分钟),方便校验。
理解 RedisTemplate
的这些操作接口和序列化机制,将使你在“苍穹外卖”项目中能够高效、灵活地利用 Redis 来提升系统性能和用户体验。