第四篇:Redis 的 Key-Value 模型到底是什么?Key 应该怎么设计?

前面三篇,我们已经把 Redis 的运行模型串了起来:

复制代码
Spring Boot 业务代码
        ↓
StringRedisTemplate
        ↓
Lettuce 等 Redis 客户端
        ↓ TCP
Redis Server
        ↓
Redis 管理的内存数据

我们也知道,执行:

复制代码
stringRedisTemplate.opsForValue()
        .set("user:name", "张三");

本质上是 Java 客户端向 Redis Server 发送了一条命令:

复制代码
SET user:name 张三

Redis 收到命令后,会在自己管理的内存中保存一条数据:

复制代码
user:name → 张三

但理解到这里之后,还会出现一系列问题:

复制代码
Redis 的 Key 到底是什么?

Value 是不是只能保存字符串?

为什么 Redis Key 经常使用冒号分隔?

多个项目共用一个 Redis 时,怎么避免 Key 冲突?

Key 是越详细越好,还是越短越好?

Redis 能不能像 MySQL 一样,通过条件查询 Key?

这些问题看起来只是命名细节,实际上会直接影响:

复制代码
数据是否容易管理
不同业务是否会冲突
缓存是否容易清理
线上问题是否容易排查
Redis 内存是否被浪费
后续系统是否容易扩展

这一篇正式进入 Redis 的数据设计阶段,重点讲清楚:

Redis 的 Key-Value 模型是什么,以及真实项目中应该怎样设计 Key。


一、Redis 最基础的数据模型

Redis 最基础的数据模型可以表示为:

复制代码
Key → Value

例如:

复制代码
user:name → 张三

其中:

复制代码
user:name
→ Key

张三
→ Value

可以暂时把它类比成 Java 中的 Map:

复制代码
Map<String, String> map = new HashMap<>();

map.put("user:name", "张三");

读取:

复制代码
String name = map.get("user:name");

Redis 中对应:

复制代码
SET user:name 张三
GET user:name

两者在使用形式上比较接近:

复制代码
Java Map
Key → Value

Redis
Key → Value

但 Redis 不是当前 Java 进程中的普通 Map。

区别在于:

复制代码
Java Map
→ 数据位于当前 JVM 进程

Redis
→ 数据位于独立的 Redis Server 进程

Java 访问 Redis 时,需要通过 Redis 客户端和网络通信。


二、Redis 中的 Key 可以理解成什么

可以把 Redis Key 理解成一条数据的唯一名称。

例如:

复制代码
user:1001:name

表示:

复制代码
用户 1001 的姓名

再例如:

复制代码
article:2001:view

表示:

复制代码
文章 2001 的阅读量

Redis 不会主动理解这些 Key 的业务含义。

对 Redis 来说:

复制代码
user:1001:name
article:2001:view
verify:code:13800000000

都只是不同的 Key。

冒号、单词和数字的业务含义,都是开发者自己设计的。

Redis 只负责:

复制代码
根据完整 Key 查找 Value

例如:

复制代码
GET user:1001:name

Redis 会查找完整名称为:

复制代码
user:1001:name

的 Key。

它不会主动分析:

复制代码
user 是用户业务
1001 是用户 ID
name 是字段名称

这些分层只是我们为了方便管理而建立的命名规范。


三、Redis Key 必须唯一吗

在同一个 Redis 逻辑数据库中,Key 是唯一的。

例如第一次执行:

复制代码
SET user:name 张三

Redis 中的数据是:

复制代码
user:name → 张三

再次执行:

复制代码
SET user:name 李四

原来的值会被覆盖:

复制代码
执行前:

user:name → 张三

执行后:

user:name → 李四

因为 Redis 不会同时保存两个完全相同的 Key。

可以类比 Java Map:

复制代码
map.put("user:name", "张三");
map.put("user:name", "李四");

最终:

复制代码
map.get("user:name");

得到的是:

复制代码
李四

所以:

同一个 Redis 数据库中,一个完整 Key 只能对应一条 Redis 数据。

如果业务 Key 设计不合理,就可能出现数据覆盖。


四、Redis 的 Value 只能是字符串吗

不是。

Redis 经常被称为 Key-Value 数据库,但这里的 Value 并不只是一段普通字符串。

Redis 常见的核心数据类型包括:

复制代码
String
Hash
List
Set
ZSet

例如,Key:

复制代码
user:1001

对应的 Value 可以是 String 类型:

复制代码
user:1001
→ {"id":1001,"name":"张三"}

也可以是 Hash 类型:

复制代码
user:1001
├── id → 1001
├── name → 张三
└── phone → 13800000000

再例如:

复制代码
article:rank

对应的 Value 可以是 ZSet,用于保存排行榜:

复制代码
文章 A → 100 分
文章 B → 80 分
文章 C → 120 分

所以,更准确的模型是:

复制代码
一个 Key
→ 对应一种 Redis 数据类型的 Value

例如:

复制代码
user:1001:name
→ String

user:1001
→ Hash

task:queue
→ List

article:liked:1001
→ Set

article:rank
→ ZSet

同一个 Key 在同一时间只能对应一种数据类型。


五、一个 Key 不能同时是两种数据类型

假设执行:

复制代码
SET user:1001 张三

此时:

复制代码
user:1001
→ String 类型

如果随后把它当成 Hash 操作:

复制代码
HSET user:1001 age 30

Redis 会返回类型错误。

因为:

复制代码
user:1001

已经是 String,不能同时又作为 Hash 使用。

这类错误通常会看到类似提示:

复制代码
WRONGTYPE
Operation against a key holding the wrong kind of value

这说明 Redis 的 Key 不只是对应一个值,还对应一个确定的数据类型。

可以使用:

复制代码
TYPE user:1001

查看 Key 的数据类型。

如果返回:

复制代码
string

表示它是 String。

如果返回:

复制代码
hash

表示它是 Hash。

因此,Key 命名时最好能够让人看出它大致保存什么数据。


六、Redis 为什么常用冒号分隔 Key

实际项目中,经常会看到这样的 Key:

复制代码
user:info:1001
login:token:abc123
verify:code:13800000000
article:view:2001

冒号在 Redis 中并没有特殊的层级语法。

下面两个 Key 对 Redis 来说没有本质区别:

复制代码
user:info:1001

user_info_1001

它们都只是一段完整字符串。

之所以通常使用冒号,是因为冒号能够让 Key 形成清晰的业务层次。

例如:

复制代码
ark:user:info:1001

可以拆解为:

复制代码
ark
→ 项目名称

user
→ 业务模块

info
→ 数据类型或业务用途

1001
→ 唯一标识

这种命名方式便于开发者阅读、排查和统一管理。

因此冒号是一种约定俗成的命名分隔符,而不是 Redis 的特殊语法。


七、推荐的 Key 基本结构

真实项目中,可以采用下面的基本结构:

复制代码
项目名:业务模块:数据用途:唯一标识

例如:

复制代码
ark:user:info:1001

其中:

复制代码
ark
→ ark-backend 项目

user
→ 用户模块

info
→ 用户信息

1001
→ 用户 ID

再例如验证码:

复制代码
ark:verify:code:13800000000

可以拆成:

复制代码
ark
→ 项目名称

verify
→ 验证业务

code
→ 验证码

13800000000
→ 手机号

登录 Token:

复制代码
ark:login:token:abc123

文章访问量:

复制代码
ark:article:view:2001

接口限流:

复制代码
ark:limit:user:1001

防重复提交:

复制代码
ark:submit:order:user:1001

这种结构的核心目的不是追求格式漂亮,而是保证:

复制代码
看见 Key 就知道属于哪个项目
看见 Key 就知道属于哪个业务
看见 Key 就知道保存什么数据
看见 Key 就知道对应哪个对象

八、为什么建议加项目名前缀

假设一台 Redis 被三个项目共同使用:

复制代码
档案项目
电梯项目
商城项目

如果三个项目都使用:

复制代码
user:1001

就可能发生冲突。

例如档案项目写入:

复制代码
SET user:1001 张三

商城项目又写入:

复制代码
SET user:1001 李四

最终档案项目原来的数据会被覆盖。

如果增加项目前缀:

复制代码
archive:user:1001
lift:user:1001
mall:user:1001

它们就是三个不同的 Key。

因此,共用 Redis 时,通常应该使用:

复制代码
项目简称:业务名称:唯一标识

例如你的项目可以使用:

复制代码
ark:user:info:1001
ark:login:token:abc123
ark:verify:code:13800000000

这样可以减少不同项目之间的数据污染。


九、Redis 有多个数据库,为什么还要加项目前缀

Redis 默认可以提供多个逻辑数据库。

有些环境中可能会看到:

复制代码
db0
db1
db2
...

可以使用:

复制代码
SELECT 1

切换到数据库 1。

看起来似乎可以这样隔离:

复制代码
项目 A 使用 db0
项目 B 使用 db1
项目 C 使用 db2

但生产环境中,通常不建议完全依赖逻辑数据库实现项目隔离。

原因是它们仍然属于:

复制代码
同一个 Redis 实例
同一个 Redis 进程
同一份服务器资源

例如某个项目大量写入数据,占满 Redis 内存,其他逻辑数据库也会受到影响。

另外,Redis Cluster 模式下通常只使用数据库 0,不支持像单机 Redis 一样自由切换多个逻辑数据库。

因此,更通用的做法是:

复制代码
重要项目使用独立 Redis 实例
或者至少使用清晰的 Key 前缀

项目名前缀仍然非常重要。


十、用户相关 Key 应该怎么设计

假设需要缓存用户 1001 的基本信息。

可以设计为:

复制代码
ark:user:info:1001

Value 可以保存用户 JSON:

复制代码
{
  "id": 1001,
  "name": "张三",
  "phone": "13800000000"
}

Redis 命令:

复制代码
SET ark:user:info:1001 "{\"id\":1001,\"name\":\"张三\"}"

Spring Boot 中可以写:

复制代码
String key = "ark:user:info:" + userId;

stringRedisTemplate.opsForValue()
        .set(key, userJson);

如果缓存用户地址:

复制代码
ark:user:address:1001

如果缓存用户权限:

复制代码
ark:user:permission:1001

如果记录用户登录失败次数:

复制代码
ark:user:login-fail:1001

同一个用户可以有多个不同用途的 Key:

复制代码
ark:user:info:1001
ark:user:address:1001
ark:user:permission:1001
ark:user:login-fail:1001

关键是每个 Key 的用途必须清晰。


十一、Token 相关 Key 应该怎么设计

假设用户登录后生成 Token:

复制代码
abc123

一种设计方式是:

复制代码
ark:login:token:abc123
→ userId 1001

Redis 命令:

复制代码
SET ark:login:token:abc123 1001 EX 7200

表示:

复制代码
Token:abc123
对应用户:1001
有效期:7200 秒

Spring Boot 中:

复制代码
String key = "ark:login:token:" + token;

stringRedisTemplate.opsForValue()
        .set(
                key,
                String.valueOf(userId),
                2,
                TimeUnit.HOURS
        );

验证 Token 时:

复制代码
String userId = stringRedisTemplate
        .opsForValue()
        .get("ark:login:token:" + token);

如果返回 null,可能表示:

复制代码
Token 不存在
Token 已经过期
Token 已经被服务端删除

也可以反向按照用户设计:

复制代码
ark:login:user:1001
→ abc123

这种设计更方便实现:

复制代码
一个用户只允许一个 Token
新设备登录时覆盖旧 Token
按用户 ID 强制下线

Key 的设计应该根据查询方向决定。


十二、验证码 Key 应该怎么设计

短信验证码通常以手机号作为唯一标识:

复制代码
ark:verify:code:13800000000

写入:

复制代码
SET ark:verify:code:13800000000 9527 EX 300

表示:

复制代码
手机号:13800000000
验证码:9527
有效期:300 秒

Spring Boot 中:

复制代码
String key = "ark:verify:code:" + phone;

stringRedisTemplate.opsForValue()
        .set(
                key,
                code,
                5,
                TimeUnit.MINUTES
        );

校验:

复制代码
String redisCode = stringRedisTemplate
        .opsForValue()
        .get(key);

验证码 Key 设计需要能够回答:

复制代码
这是什么业务的验证码?
这是哪个用户或手机号的验证码?
验证码有效期是多少?

如果同一个系统有多种验证码,还可以继续细分:

复制代码
ark:verify:login:13800000000
ark:verify:register:13800000000
ark:verify:reset-password:13800000000

否则不同业务可能相互覆盖。

例如:

复制代码
登录验证码

和:

复制代码
修改密码验证码

都使用:

复制代码
ark:verify:code:13800000000

后发送的验证码会覆盖前一个。

因此,应根据业务场景增加类型:

复制代码
项目名:验证码业务:场景:手机号

十三、文章访问量 Key 应该怎么设计

记录文章 2001 的访问量,可以设计为:

复制代码
ark:article:view:2001

初始化:

复制代码
SET ark:article:view:2001 0

每访问一次:

复制代码
INCR ark:article:view:2001

查看:

复制代码
GET ark:article:view:2001

这里的 Key 结构是:

复制代码
ark
→ 项目

article
→ 文章模块

view
→ 阅读量

2001
→ 文章 ID

如果还要记录点赞量:

复制代码
ark:article:like:2001

评论数:

复制代码
ark:article:comment:2001

收藏量:

复制代码
ark:article:favorite:2001

这样的 Key 结构比较清晰。


十四、Key 中应该使用用户 ID 还是手机号

假设要保存用户缓存,可以选择:

复制代码
ark:user:info:1001

也可以选择:

复制代码
ark:user:info:13800000000

通常更推荐使用稳定、唯一且较短的内部 ID:

复制代码
ark:user:info:1001

原因包括:

1. 用户 ID 通常更加稳定

手机号可能会更换。

用户 ID 通常不会变化。

2. 避免敏感信息直接出现在 Key 中

手机号、身份证号、邮箱等信息直接出现在 Redis Key 中,可能增加日志和运维排查过程中的隐私暴露风险。

3. 用户 ID 通常更短

短 Key 可以减少一定内存占用。

因此,正式业务数据通常优先使用:

复制代码
内部唯一 ID

但验证码场景天然需要通过手机号查询,此时手机号作为 Key 的一部分是合理的。

Key 设计需要根据实际查询方式决定。


十五、Key 是越长越好吗

不是。

一个极其详细的 Key 可能是:

复制代码
ark-backend-project:user-module:user-information-cache:user-id:1001

它确实非常清晰,但也过于冗长。

Redis 中通常会存在大量 Key。

如果每个 Key 都很长,就会增加内存占用。

假设有一百万个 Key,每个 Key 多出几十个字符,整体内存差异就会非常明显。

因此,Key 设计需要在:

复制代码
可读性
和
空间占用

之间取得平衡。

例如:

复制代码
ark:user:info:1001

通常已经足够表达业务含义。

没有必要写成:

复制代码
ark-backend-system:user-management-module:user-basic-information-cache:1001

推荐原则是:

Key 应该清晰,但不要为了描述完整而无限增长。


十六、Key 是越短越好吗

也不是。

例如把用户缓存设计成:

复制代码
u:1001

确实很短。

但团队成员看到它时,可能无法确定:

复制代码
u 是 user 还是 upload?
保存的是用户信息还是用户状态?
这是哪个项目的用户?

再例如:

复制代码
t:a1

几乎无法通过 Key 判断业务含义。

这种 Key 虽然节省少量空间,但会增加维护成本。

线上排查时,运维或开发看到:

复制代码
u:1001
t:a1
v:2001

很难判断它们分别是什么。

因此:

复制代码
Key 太长
→ 浪费内存,书写复杂

Key 太短
→ 缺少业务含义,难以维护

更合理的是使用稳定、简洁、可读的缩写:

复制代码
ark:user:info:1001
ark:login:token:abc123
ark:article:view:2001

十七、Key 命名应该统一大小写

Redis Key 区分大小写。

下面是三个不同的 Key:

复制代码
user:1001
User:1001
USER:1001

Redis 不会把它们当成同一个 Key。

例如:

复制代码
SET user:1001 张三
SET User:1001 李四

此时 Redis 中会存在两条数据。

因此,团队必须统一大小写规范。

通常推荐全部使用小写:

复制代码
ark:user:info:1001

不要混用:

复制代码
Ark:User:Info:1001
ARK:user:INFO:1001

全小写的优势是:

复制代码
风格统一
减少输入错误
避免大小写冲突
便于团队协作

十八、Key 中应该使用空格吗

虽然 Redis 的 Key 可以包含很多字符,但不建议在业务 Key 中使用空格。

例如:

复制代码
ark user info 1001

在命令行中需要额外处理:

复制代码
SET "ark user info 1001" 张三

这会增加:

复制代码
命令输入难度
日志阅读难度
脚本处理难度
排查问题的复杂度

因此一般使用:

复制代码
冒号
短横线
下划线

其中最常见的是冒号:

复制代码
ark:user:info:1001

业务单词内部可以使用短横线:

复制代码
ark:user:reset-password:1001

核心是保持团队统一。


十九、Key 中能不能使用中文

Redis 的 Key 本质上可以保存字节数据,因此技术上可以使用中文。

例如:

复制代码
SET 用户:1001:姓名 张三

但生产项目中一般不建议大量使用中文 Key。

原因包括:

复制代码
不同工具显示可能不一致
脚本处理不方便
跨语言协作不方便
编码问题更难排查
命令行输入效率较低

更推荐:

复制代码
ark:user:name:1001

Value 中保存中文通常没有问题:

复制代码
SET ark:user:name:1001 张三

也就是:

复制代码
Key
→ 建议使用规范英文和数字

Value
→ 根据业务正常保存中文内容

二十、Key 能不能像数据库字段一样模糊查询

Redis 的主要使用方式是通过完整 Key 查询。

例如:

复制代码
GET ark:user:info:1001

Redis 可以快速找到对应数据。

但是有时我们可能想查询:

复制代码
所有 ark:user 开头的 Key

Redis 提供了相关命令,例如:

复制代码
KEYS ark:user:*

它可能返回:

复制代码
ark:user:info:1001
ark:user:info:1002
ark:user:address:1001
ark:user:permission:1001

但生产环境中要非常谨慎使用 KEYS


二十一、为什么生产环境不建议随便使用 KEYS

KEYS 会扫描符合模式的所有 Key。

例如:

复制代码
KEYS *

表示查找当前数据库中的所有 Key。

如果 Redis 中只有几十个 Key,问题可能不明显。

但如果 Redis 中有:

复制代码
几十万
几百万
甚至更多 Key

执行 KEYS * 可能占用 Redis 较长时间。

Redis 在处理这个命令期间,其他请求可能受到影响。

因此生产环境中通常不建议随意执行:

复制代码
KEYS *

或者:

复制代码
KEYS ark:user:*

尤其是在数据量较大时。

Redis 不是为了让我们像数据库一样频繁模糊搜索 Key 而设计的。

正确思路应该是:

在写入数据之前,就设计好明确、可直接计算出来的 Key。

例如通过用户 ID 得到:

复制代码
String key = "ark:user:info:" + userId;

然后直接查询:

复制代码
stringRedisTemplate.opsForValue().get(key);

二十二、需要遍历 Key 时使用什么

如果确实需要逐步扫描 Key,可以使用:

复制代码
SCAN

例如:

复制代码
SCAN 0 MATCH ark:user:* COUNT 100

SCAN 会以游标方式分批返回结果,而不是一次性扫描完所有 Key。

可以简单理解为:

复制代码
KEYS
→ 一次性找完

SCAN
→ 分批逐步查找

SCAN 对线上环境相对友好,但也不代表可以毫无成本地频繁使用。

应用业务代码中,最好仍然通过明确的 Key 直接访问。

如果某个业务经常需要:

复制代码
查询所有用户缓存
查询某类全部 Key
按照多个条件筛选

可能说明数据结构设计不合理,或者这部分需求更适合数据库、Set、ZSet 等结构。


二十三、如何检查一个 Key 是否存在

可以使用:

复制代码
EXISTS ark:user:info:1001

如果 Key 存在,返回:

复制代码
1

如果不存在,返回:

复制代码
0

Spring Boot 中可以写:

复制代码
Boolean exists = stringRedisTemplate
        .hasKey("ark:user:info:1001");

但实际业务中不要为了查询一个值,先执行:

复制代码
EXISTS

再执行:

复制代码
GET

例如下面这种写法可能会产生两次 Redis 请求:

复制代码
if (Boolean.TRUE.equals(
        stringRedisTemplate.hasKey(key)
)) {
    return stringRedisTemplate
            .opsForValue()
            .get(key);
}

很多情况下可以直接:

复制代码
String value = stringRedisTemplate
        .opsForValue()
        .get(key);

if (value == null) {
    // Key 不存在或已经过期
}

这样只需要一次访问。

EXISTS 适合确实只关心 Key 是否存在的场景。


二十四、如何查看 Key 的数据类型

可以使用:

复制代码
TYPE ark:user:info:1001

可能返回:

复制代码
string

或者:

复制代码
hash
list
set
zset
none

如果返回:

复制代码
none

表示 Key 不存在。

Spring Boot 中也可以获取类型,但在日常业务中一般不会频繁动态判断。

更合理的是:

在 Key 设计阶段就确定每类 Key 对应的数据类型。

例如统一约定:

复制代码
ark:user:info:{userId}
→ String,保存用户 JSON

ark:user:profile:{userId}
→ Hash,保存用户字段

ark:article:liked:{articleId}
→ Set,保存点赞用户 ID

ark:article:rank
→ ZSet,保存文章排行榜

不要让同一个业务 Key 在不同代码中被当成不同类型使用。


二十五、Key 是否应该设置过期时间

不是所有 Key 都必须过期,但大多数缓存数据都应该考虑过期时间。

例如用户信息缓存:

复制代码
ark:user:info:1001

可以设置 30 分钟过期:

复制代码
SET ark:user:info:1001 "{...}" EX 1800

验证码:

复制代码
ark:verify:login:13800000000

设置 5 分钟过期:

复制代码
SET ark:verify:login:13800000000 9527 EX 300

Token:

复制代码
ark:login:token:abc123

设置 2 小时过期:

复制代码
SET ark:login:token:abc123 1001 EX 7200

如果缓存数据永不过期,就可能出现:

复制代码
旧数据长期存在
无用 Key 越来越多
Redis 内存持续增长
数据库数据已经更新,但缓存仍然陈旧

因此设计 Key 时,不仅要考虑名称,还应该同时考虑:

复制代码
Value 类型
过期时间
更新方式
删除方式
数据来源

二十六、Key 删除后会发生什么

删除 Key:

复制代码
DEL ark:user:info:1001

之后:

复制代码
GET ark:user:info:1001

会返回空结果。

在缓存场景中,删除 Key 并不代表正式用户数据被删除。

例如:

复制代码
MySQL
→ 仍然保存用户 1001

Redis
→ 用户缓存被删除

下一次请求可能会:

复制代码
先查询 Redis
        ↓
Redis 没有
        ↓
查询 MySQL
        ↓
重新写入 Redis

因此删除缓存 Key 的含义通常是:

让当前缓存副本失效,下一次重新从正式数据源加载。

这和删除 MySQL 中的正式数据完全不同。


二十七、Key 设计必须考虑如何删除

假设用户退出登录,需要删除 Token:

复制代码
ark:login:token:abc123

这个 Key 很容易计算:

复制代码
String key = "ark:login:token:" + token;

然后删除:

复制代码
stringRedisTemplate.delete(key);

但如果 Key 设计混乱,例如:

复制代码
abc123-token-login-user-1001-ark

虽然也能工作,但代码维护和问题排查会更加困难。

好的 Key 设计应该同时支持:

复制代码
容易生成
容易查询
容易删除
容易判断用途
容易统一设置过期时间

不能只考虑写入时是否方便。


二十八、不要把所有参数都塞进 Key

假设有一个商品查询接口:

复制代码
分类:手机
品牌:华为
价格区间:3000~5000
排序:销量降序
页码:1
每页:20

如果直接把所有参数拼进 Key:

复制代码
mall:product:list:category-phone:brand-huawei:
price-3000-5000:sort-sales-desc:page-1:size-20

Key 会变得非常长,而且参数顺序、空值处理、字符编码都可能产生问题。

复杂查询缓存通常需要:

复制代码
规范化请求参数
固定参数顺序
对参数生成摘要
控制缓存维度
防止产生海量不同 Key

例如可以将规范化参数计算哈希:

复制代码
mall:product:list:7f83a2...

但这种做法会降低可读性,需要配合日志和文档。

目前学习阶段只需要知道:

简单实体缓存可以直接使用业务 ID;复杂查询缓存不能无脑拼接所有参数。


二十九、避免缓存 Key 数量无限增长

假设搜索接口按照搜索词缓存:

复制代码
ark:search:keyword:redis
ark:search:keyword:mysql
ark:search:keyword:spring

如果任何用户输入都生成一个新 Key,可能出现:

复制代码
用户输入一次
→ 生成一个新 Key

不同搜索词越来越多
→ Redis Key 数量持续增长

这种问题称为缓存空间失控的一种表现。

因此 Key 设计还要考虑:

复制代码
业务可能产生多少个 Key
Key 是否会自动过期
是否存在用户恶意输入
是否有最大数量限制
是否真的值得缓存

Redis Key 设计不是简单的字符串命名,而是一种数据规模设计。


三十、推荐建立统一的 Key 常量

Spring Boot 项目中,不建议在业务代码里到处手写:

复制代码
"ark:user:info:"

例如:

复制代码
String key = "ark:user:info:" + userId;

另一个类又写:

复制代码
String key = "ark:user:information:" + userId;

这可能导致同一个业务产生两套 Key。

可以建立统一常量:

复制代码
public final class RedisKeyConstants {

    private RedisKeyConstants() {
    }

    public static final String PROJECT_PREFIX = "ark";

    public static final String USER_INFO =
            PROJECT_PREFIX + ":user:info:";

    public static final String LOGIN_TOKEN =
            PROJECT_PREFIX + ":login:token:";

    public static final String VERIFY_LOGIN =
            PROJECT_PREFIX + ":verify:login:";

    public static final String ARTICLE_VIEW =
            PROJECT_PREFIX + ":article:view:";
}

使用:

复制代码
String key =
        RedisKeyConstants.USER_INFO + userId;

这样可以统一维护。


三十一、进一步封装 Key 构建方法

如果 Key 结构较多,可以进一步封装:

复制代码
public final class RedisKeys {

    private static final String PROJECT = "ark";

    private RedisKeys() {
    }

    public static String userInfo(Long userId) {
        return PROJECT + ":user:info:" + userId;
    }

    public static String loginToken(String token) {
        return PROJECT + ":login:token:" + token;
    }

    public static String loginVerifyCode(String phone) {
        return PROJECT + ":verify:login:" + phone;
    }

    public static String articleView(Long articleId) {
        return PROJECT + ":article:view:" + articleId;
    }
}

使用:

复制代码
String key = RedisKeys.userInfo(1001L);

得到:

复制代码
ark:user:info:1001

这种方式的优势是:

复制代码
统一命名
减少字符串拼写错误
方便批量调整前缀
业务含义更加清晰
更容易编写测试

三十二、是否要把环境名称加进 Key

实际项目可能有多个环境:

复制代码
开发环境
测试环境
预发布环境
生产环境

如果它们错误地共用同一个 Redis,就可能互相覆盖数据。

可以在 Key 中增加环境前缀:

复制代码
dev:ark:user:info:1001
test:ark:user:info:1001
prod:ark:user:info:1001

但更合理的部署方式通常是:

复制代码
不同环境使用不同 Redis 实例

因为即使增加 Key 前缀,它们仍然共用:

复制代码
同一份 Redis 内存
同一套资源
同一套故障范围

环境前缀可以作为额外保护,但不能代替环境隔离。


三十三、一个实用的 Key 设计规范

对于当前阶段,可以先使用下面这套规范。

1. 全部小写

复制代码
ark:user:info:1001

避免:

复制代码
ARK:User:Info:1001

2. 使用冒号分层

复制代码
项目:模块:用途:唯一标识

3. 使用稳定唯一标识

优先:

复制代码
用户 ID
订单 ID
文章 ID
设备 ID

4. 不使用无意义缩写

推荐:

复制代码
ark:user:info:1001

谨慎使用:

复制代码
a:u:i:1001

5. 不包含不必要的敏感信息

尽量避免直接放入:

复制代码
身份证号
完整手机号
密码
密钥
Token 明文日志

Token 作为 Key 的组成部分有时难以避免,但日志中要注意脱敏。

6. 明确数据类型

例如提前约定:

复制代码
ark:user:info:{id}
→ String JSON

7. 明确过期时间

例如:

复制代码
用户缓存:30分钟
验证码:5分钟
Token:2小时
防重复提交:10秒

8. Key 必须可以直接计算

尽量避免业务代码依赖模糊扫描才能找到数据。


三十四、为 ark-backend 设计一组 Key

结合你的后端项目,可以先设计下面这组 Key。

用户信息缓存

复制代码
ark:user:info:{userId}

例如:

复制代码
ark:user:info:1001

用户地址缓存

复制代码
ark:user:address:{userId}

登录 Token

复制代码
ark:login:token:{token}

用户当前 Token

复制代码
ark:login:user:{userId}

登录验证码

复制代码
ark:verify:login:{phone}

注册验证码

复制代码
ark:verify:register:{phone}

登录失败次数

复制代码
ark:login:fail:{account}

防重复提交

复制代码
ark:submit:{business}:{userId}

例如:

复制代码
ark:submit:create-order:1001

接口限流

复制代码
ark:limit:{api}:{userId}

例如:

复制代码
ark:limit:send-code:1001

文章访问量

复制代码
ark:article:view:{articleId}

通过这一组 Key,可以看到统一结构:

复制代码
ark
→ 项目名称

第二段
→ 业务模块

第三段
→ 数据用途

最后一段
→ 唯一标识

三十五、Redis Key 设计不是数据库表设计

MySQL 中可能有:

复制代码
user 表

字段包括:

复制代码
id
name
phone
status
create_time

Redis 中不一定要为每个字段单独设计 Key:

复制代码
ark:user:name:1001
ark:user:phone:1001
ark:user:status:1001
ark:user:create-time:1001

这样会产生大量 Key,并增加多次网络请求。

更常见的是把用户作为一个整体缓存:

复制代码
ark:user:info:1001
→ 用户 JSON

或者使用 Hash:

复制代码
ark:user:info:1001
├── name → 张三
├── phone → 13800000000
└── status → 1

因此 Redis Key 的粒度需要根据:

复制代码
数据读取方式
字段更新频率
数据大小
网络请求次数
缓存失效方式

综合判断。

不要机械地把 MySQL 每一行、每一列都转换成 Redis Key。


三十六、Redis 适合通过 Key 精准访问

MySQL 的典型访问方式是:

复制代码
SELECT *
FROM user
WHERE status = 1
  AND create_time > '2026-01-01';

Redis 更典型的访问方式是:

复制代码
GET ark:user:info:1001

也就是:

复制代码
已经知道唯一 Key
→ 直接获取数据

如果业务需求是:

复制代码
按多个条件筛选
关联多张表
动态排序
复杂分页
聚合统计

通常更适合 MySQL、Elasticsearch 等系统。

Redis 更擅长:

复制代码
根据明确 Key 获取数据
根据明确成员操作集合
按照分数查询排行榜
进行计数和临时状态管理

所以 Redis Key 的核心设计目标是:

让业务代码能够根据已知参数直接计算出目标 Key。


三十七、本篇动手实践

打开 redis-cli,执行下面的命令。

1. 保存用户信息

复制代码
SET ark:user:info:1001 "{\"id\":1001,\"name\":\"张三\"}"

读取:

复制代码
GET ark:user:info:1001

检查是否存在:

复制代码
EXISTS ark:user:info:1001

查看类型:

复制代码
TYPE ark:user:info:1001

2. 保存验证码

复制代码
SET ark:verify:login:13800000000 9527 EX 300

读取:

复制代码
GET ark:verify:login:13800000000

查看剩余时间:

复制代码
TTL ark:verify:login:13800000000

3. 记录访问量

复制代码
SET ark:article:view:2001 0

增加:

复制代码
INCR ark:article:view:2001

读取:

复制代码
GET ark:article:view:2001

4. 查看当前 Key

学习环境中可以执行:

复制代码
KEYS ark:*

但需要记住:

生产环境不要随意使用 KEYS *


三十八、本篇总结

这一篇正式介绍了 Redis 的 Key-Value 数据模型。

Redis 中的每条数据都通过唯一 Key 标识:

复制代码
Key → Value

但 Value 不只可以是普通字符串,还可以是:

复制代码
String
Hash
List
Set
ZSet

同一个 Key 在同一时间只能对应一种 Redis 数据类型。

Redis Key 中常见的冒号并不是特殊语法,而是一种方便管理的命名约定。

推荐的基础结构是:

复制代码
项目名:业务模块:数据用途:唯一标识

例如:

复制代码
ark:user:info:1001
ark:login:token:abc123
ark:verify:login:13800000000
ark:article:view:2001

设计 Key 时需要同时考虑:

复制代码
唯一性
可读性
长度
大小写
数据类型
过期时间
查询方式
删除方式
数据规模

Key 不能过长,也不能为了节省几个字符而失去业务含义。

Redis 最擅长通过完整 Key 精准访问数据,而不是像 MySQL 一样频繁进行复杂条件查询。

最终可以用一句话总结:

Redis Key 是数据在 Redis 中的唯一业务地址。好的 Key 设计应该让程序能够直接计算、准确查询、方便删除,并让开发者看到 Key 就能判断它属于哪个项目、哪个业务以及哪一条数据。


下一篇预告

下一篇继续学习 Redis 最常用的数据类型:

《Redis String 类型和过期时间 TTL:验证码为什么会自动失效?》

下一篇会重点讲清楚:

复制代码
Redis String 到底能保存什么
SET 和 GET 的常见用法
SETEX、EX、PX 分别是什么
EXPIRE 如何设置过期时间
TTL 的返回值为什么有 -1 和 -2
验证码到期后是谁删除的
Redis 如何判断一个 Key 已经过期
INCR 为什么可以安全计数
Token、验证码和缓存应该如何设置有效期

下一篇开始,我们会使用 String 完成 Redis 中最常见的几类业务操作。

相关推荐
ai_coder_ai8 小时前
分布式缓存架构设计与实践
分布式·缓存
凡尘——雨落凡尘8 小时前
PHP 线上十大隐形故障复盘:90% 的网站卡顿、502、雪崩,都是这些细节导致的
redis·nginx·php·session
一水11 小时前
Redis实战:一个AI工作流系统里的五个应用场景,从传参到限流的完整链路
数据库·redis·wpf
Wang's Blog13 小时前
AI Agent白手起家23: 大模型速率限制应对与缓存机制实践
人工智能·缓存
鹿角片ljp13 小时前
Redis 深度复习:从入门到面试通关
数据库·redis·面试
网教盟人才服务平台15 小时前
Redis Key集中过期引发的流量雪崩实战解析
数据库·redis·缓存
Ivan CloudBay15 小时前
网站为什么会使用缓存?
服务器·网络·缓存·云服务器
Linux运维技术栈15 小时前
业务不停机、数据零丢失:Redis / RabbitMQ / Elasticsearch 三大核心中间件升级改造集群无缝平滑迁移
redis·elasticsearch·rabbitmq
_oP_i15 小时前
Another Redis Desktop Manager更新
数据库·redis·缓存
小林ixn16 小时前
Redis 实战避坑指南:从缓存击穿到高可用,一文全搞定
redis·后端