一、Redis 概述
1.1 什么是 Redis
Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能 Key-Value 数据库。
|----------|--------------|-----------|
| 特性 | Redis | MySQL |
| 存储介质 | 内存(RAM) | 磁盘(Disk) |
| 数据结构 | Key-Value | 二维表(关系型) |
| 读写性能 | 极高(10万+ QPS) | 中等 |
| 持久化 | 支持(RDB/AOF) | 天然支持 |
| 适用场景 | 缓存、会话、实时数据 | 事务性数据存储 |

1.2 核心优势
- ⚡ 高性能:纯内存操作,读写速度极快(10万次/秒以上)
- 🎯 适合热点数据:短时间内被高频访问的数据(热点商品、资讯、新闻、排行榜等)
- 💾 数据类型丰富:支持字符串、哈希、列表、集合、有序集合等
- 🔄 持久化机制:支持 RDB 快照和 AOF 日志,防止数据丢失
- 📊 原子操作:所有操作都是原子性的,支持事务
二、数据类型详解
2.1 基础结构
Key(字符串) → Value(五种数据类型)

2.2 五种常用数据类型对比
|------------|------------|-----------------------|---------------------|------------|
| 数据类型 | 结构描述 | Java 类比 | 适用场景 | 最大容量 |
| String | 二进制安全字符串 | String | 缓存、计数器、分布式锁、Session | 512 MB |
| Hash | 键值对集合 | HashMap | 存储对象(用户信息、商品信息) | 2³²-1 个键值对 |
| List | 双向链表,有序可重复 | LinkedList | 消息队列、时间线、最新列表 | 2³²-1 个元素 |
| Set | 无序集合,唯一不重复 | HashSet | 标签、共同好友、去重 | 2³²-1 个成员 |
| ZSet | 有序集合,带分数排序 | TreeSet + HashMap | 排行榜、延迟队列、权重排序 | 2³²-1 个成员 |
📌 String(字符串)
# 基础操作
SET user:1001 "张三"
GET user:1001
SETEX session:abc 3600 "login_data" # 设置过期时间(秒)
INCR view_count:article:123 # 原子自增
特点:二进制安全,可存储图片、序列化对象、JSON 字符串等任意数据
📌 Hash(哈希/散列)
# 存储对象
HSET user:1001 name "张三" age 25 city "北京"
HGET user:1001 name
HGETALL user:1001 # 获取所有字段
优势:比 String 更节省内存,适合存储对象属性
📌 List(列表)
# 双向链表操作
LPUSH news:list "头条新闻" # 左侧插入(最新)
RPUSH news:list "旧新闻" # 右侧插入(最旧)
LRANGE news:list 0 9 # 获取前10条(分页)
LPOP news:list # 左侧弹出(消费)
特点:支持阻塞读取(BLPOP/BRPOP),可实现简单消息队列
📌 Set(集合)
# 无序唯一集合
SADD tags:article:123 "Java" "Redis" "后端"
SISMEMBER tags:article:123 "Java" # 判断是否包含
SINTER user:1001:follows user:1002:follows # 共同关注(交集)
特点:基于哈希表实现,增删查复杂度都是 O(1)
📌 ZSet(Sorted Set / 有序集合)
# 带权重的有序集合
ZADD leaderboard 100 "player:张三" 85 "player:李四" 120 "player:王五"
ZRANGE leaderboard 0 2 WITHSCORES # 升序取前3名(分数低到高)
ZREVRANGE leaderboard 0 2 WITHSCORES # 降序取前3名(排行榜)
ZINCRBY leaderboard 10 "player:张三" # 增加分数
关键:每个元素关联一个 double 类型的 score,按 score 排序;成员唯一,但 score 可重复
三、常用命令速查
key-value结构
key是字符串类型
value有五种常用的数据类型:
字符串(string)
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
哈希(hash) 也叫散列,类似于Java的HashMap, 适合存储对象
列表(list) 按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。
三、常用命令速查
📚 完整命令参考:Redis 命令中心
|------------|----------------------------------------------------------------------------|
| 操作类型 | 常用命令 |
| 键操作 | KEYS pattern, EXISTS key, DEL key , EXPIRE key seconds, TTL key |
| String | SET, GET, MSET, MGET, INCR, DECR , APPEND |
| Hash | HSET, HGET, HMSET, HGETALL, HDEL , HINCRBY |
| List | LPUSH, RPUSH, LPOP, RPOP, LRANGE , LLEN, LINDEX |
| Set | SADD, SREM, SMEMBERS, SISMEMBER , SINTER, SUNION, SCARD |
| ZSet | ZADD, ZRANGE, ZREVRANGE , ZRANGEBYSCORE, ZREM, ZCARD, ZSCORE |
| 事务 | MULTI, EXEC, DISCARD, WATCH |
| 持久化 | SAVE, BGSAVE, LASTSAVE |
四、Spring Data Redis 实战
4.1 快速开始
步骤 1:添加 Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
步骤 2:配置数据源(application.yml)
//application.yml
spring:
profiles:
active: dev
redis:
host: ${sky.redis.host}
port: ${sky.redis.port}
database: ${sky.redis.database}
//application-dev.yaml
sky:
redis:
host: localhost
port: 6379
password: # 如有密码
database: 0 # 默认数据库
步骤 3:配置 RedisTemplate(⚠️ 关键)
@Slf4j
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("创建RedisTemplate对象...");
RedisTemplate redisTemplate = new RedisTemplate();
// 设置redis的连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
步骤 4:使用 RedisTemplate
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// String 操作
public void setString(String key, Object value) {
redisTemplate.opsForValue().set(key, value, Duration.ofHours(1));
}
// Hash 操作(适合存对象)
public void setHash(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
// List 操作
public void pushToList(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
}
4.2 序列化器详解
序列化器是 Redis 客户端用来将 Java 对象转换为字节流(用于存储到 Redis)以及将字节流转换回 Java 对象(从 Redis 读取时)的工具。
|--------------------------------------|--------------|--------------------|
| 序列化器 | 说明 | 适用场景 |
| StringRedisSerializer | 字符串序列化 | Key 的序列化(可读性好) |
| GenericJackson2JsonRedisSerializer | JSON 格式 | Value 的序列化(跨语言、可读) |
| JdkSerializationRedisSerializer | Java 原生序列化 | 默认,但不推荐(二进制不可读) |
| GenericToStringSerializer | 转 String 序列化 | 简单数值类型 |
💡 建议 :Key 用 StringRedisSerializer,Value 用 GenericJackson2JsonRedisSerializer,避免内存中出现 \xac\xed\x00 这样的乱码前缀。
五、典型应用场景
店铺营业状态存储(热点数据案例)
进到苍穹外卖后台,显示餐厅的营业状态,营业状态分为营业中 和打烊中,若当前餐厅处于营业状态,自动接收任何订单,客户可在小程序进行下单操作;若当前餐厅处于打烊状态,不接受任何订单,客户便无法在小程序进行下单操作。

场景分析:
- 营业状态被客户端频繁查询(每次进入首页都要请求)
- 数据量小(只是一个状态值:0/1)
- 需要快速响应
接口设计:
- 设置营业状态
- 管理端查询营业状态
- 用户端查询营业状态
注:从技术层面分析,其实管理端和用户端查询营业状态时,可通过一个接口去实现即可。因为营业状态是一致的。但是,本项目约定:
- 管理端发出的请求,统一使用
/admin作为前缀。 - 用户端发出的请求,统一使用
/user作为前缀。
因为访问路径不一致,故分为两个接口实现。
管理端:
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置营业状态
* @param status
* @return
*/
@PutMapping("/{status}")
@ApiOperation("设置营业状态")
public Result setStatus(@PathVariable Integer status) {
log.info("设置店铺营业状态:{}", status == 1 ? "营业中" : "打烊中");
redisTemplate.opsForValue().set("SHOP_STATUS", status);
return Result.success();
}
/**
* 获取营业状态
* @return
*/
@ApiOperation("获取营业状态")
@GetMapping("/status")
public Result<Integer> getStatus() {
Integer status = (Integer)redisTemplate.opsForValue().get("SHOP_STATUS");
log.info("获取营业状态:{}", status == 1 ? "营业中" : "打烊中");
return Result.success(status);
}
}
用户端:
@RestController("userShopController")
@RequestMapping("/user/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
@Autowired
private RedisTemplate redisTemplate;
/**
* 获取营业状态
* @return
*/
@ApiOperation("获取营业状态")
@GetMapping("/status")
public Result<Integer> getStatus() {
Integer status = (Integer)redisTemplate.opsForValue().get("SHOP_STATUS");
log.info("获取营业状态:{}", status == 1 ? "营业中" : "打烊中");
return Result.success(status);
}
}
六、Spring Boot 开发技巧
6.1 Controller 命名冲突解决
问题 :管理端和用户端都有 ShopController,Spring Bean 名称冲突。
// 管理端
@RestController("adminShopController") // ✅ 指定 Bean 名称
@RequestMapping("/admin/shop")
public class ShopController {
// ...
}
// 用户端
@RestController("userShopController") // ✅ 指定不同名称
@RequestMapping("/user/shop")
public class ShopController {
// ...
}
原理:
- Spring 默认使用类名首字母小写 作为 Bean 名称(
shopController) - 同名类会产生冲突,通过
@RestController("指定名称")显式命名 - 便于
@Autowired精确注入,提高代码可维护性
6.2 Knife4j 接口文档分组配置
需求:将管理端和用户端接口分离展示,方便前后端协作。
/**
* 通过knife4j生成接口文档
*
* @return
*/
@Bean
public Docket docket() {
log.info("开始创建knife4j接口文档...");
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("苍穹外卖管理端")
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
.paths(PathSelectors.any())
.build();
return docket;
}
/**
* 通过knife4j生成接口文档
*
* @return
*/
@Bean
public Docket docket2() {
log.info("开始创建knife4j接口文档...");
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("苍穹外卖客户端")
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
.paths(PathSelectors.any())
.build();
return docket;
}
效果:
- 访问
/doc.html时,右上角可切换 "苍穹外卖-管理端" 和 "苍穹外卖-用户端" - 接口按业务模块清晰分组,避免混杂
七、Redis 最佳实践 & 注意事项
7.1 Key 命名规范
# 推荐格式:业务模块:实体:标识
user:1001:profile # 用户资料
order:2024:1001 # 订单信息
cache:article:123:list # 缓存的文章列表
# 避免
key1, a, bbb # 无意义命名
7.2 常见问题
|--------------|-------------------------|
| 问题 | 解决方案 |
| 缓存穿透 | 布隆过滤器 或 缓存空值 |
| 缓存击穿 | 互斥锁(SETNX)或 逻辑过期 |
| 缓存雪崩 | 随机过期时间 + 集群部署 |
| Key 膨胀 | 设置合理的过期时间(TTL) |
| 大 Key 问题 | 拆分存储,避免单个 Value 超过 10KB |
7.3 性能优化建议
- Pipeline 批量操作:减少网络往返次数
- 连接池配置:使用 Lettuce 或 Jedis 连接池
- 避免全量操作 :慎用
KEYS *,使用SCAN代替 - 合理设置 TTL:热点数据设置过期时间,冷数据及时清理