掌握 Redis GET 和 SET:基础数据操作
通过这份关于 `GET` 和 `SET` 命令的全面指南,掌握 Redis 数据管理的基础。学习基本的字符串存储与检索,并探索关键的高级选项,如原子性设置(`NX`/`XX`)和集成键过期(`EX`/`PX`)。了解这些基础命令如何对构建高性能缓存层至关重要。
掌握 Redis GET 和 SET:基础数据操作
Redis 的 GET 和 SET 看起来几乎过于简单。写入一个值。读取一个值。在实际应用中,这两个命令支撑着登录会话、功能开关、速率限制、缓存条目、短期锁以及“请不要再查询数据库”的快捷方式。
细节很重要,因为 Redis 会精确执行你的指令。如果你意外覆盖了一个键,它不会询问你是否是有意的。如果你忘记为缓存数据设置过期时间,它可能比数据源存活更久。如果你将缺失的键与空字符串等同处理,你的应用程序可能会做出错误的决策。
Redis 键值模型
在深入命令之前,重要的是要记住 Redis 运行在简单的键值存储模型上。每条数据(值)都通过一个唯一标识符(键)来访问。键是字符串,值可以是多种数据类型(字符串、列表、集合、哈希等)。SET 和 GET 主要处理字符串数据类型,这是 Redis 中最基本且最常用的类型。
1. 设置数据:SET 命令
SET 命令用于为键赋值。如果该键已持有数据,SET 命令将覆盖现有值。其基本语法很直接。
基本语法和用法
最简单的形式只需要键和值:
SET key value
示例:存储用户的显示名称:
127.0.0.1:6379> SET user:100:name "Alice Johnson"
OK
127.0.0.1:6379> GET user:100:name
"Alice Johnson"
高级 SET 选项:NX、XX 和过期时间
SET 的强大之处在于其可选参数,这些参数允许原子性的条件设置和生存时间(TTL)管理。这些选项对于正确实现锁和缓存至关重要。
条件设置:NX 和 XX
这些选项控制何时执行设置操作,防止意外覆盖或确保仅当键存在时才进行覆盖。
NX(不存在): 仅当键不存在时才设置。这对于“仅创建”操作和简单的锁模式很有用。SET my_lock_key some_unique_value NXXX(存在): 仅当键已经存在时才设置。当你想要刷新一个已知键而不意外创建新键时,这很有用。SET session:token:456 new_value XX
B. 设置过期时间(TTL)
为了管理内存并实现基于时间的缓存,你可以直接在 SET 命令中设置过期时间。这比先设置键再单独调用 EXPIRE 高效得多。
EX seconds: 以秒为单位设置过期时间。PX milliseconds: 以毫秒为单位设置过期时间。EXAT timestamp: 将过期时间设置为特定的 Unix 时间戳(秒)。PXAT timestamp: 将过期时间设置为特定的 Unix 时间戳(毫秒)。
示例:设置一个键在一小时后过期:
127.0.0.1:6379> SET cache:product:500 "Product Details" EX 3600
OK
127.0.0.1:6379> TTL cache:product:500
(integer) 3598
对于缓存条目,使用 SET key value EX N 或 PX N。它将写入和过期操作合并为一个命令,避免了应用程序写入缓存键后在调用 EXPIRE 之前崩溃的常见错误。
组合选项
所有选项通常可以组合用于复杂的原子操作:
# 仅当键不存在时设置,并使其在 60 秒后过期
SET my_config_setting "active" NX EX 60
2. 检索数据:GET 命令
GET 命令检索与给定键关联的字符串值。它是 Redis 执行最快的操作之一,通常在微秒内完成。
基本语法和用法
GET key
示例: 检索存储的用户名
127.0.0.1:6379> GET user:100:name
"Alice Johnson"
处理不存在的键
如果键不存在,GET 返回一个特殊响应,指示未找到任何内容:
127.0.0.1:6379> GET non_existent_key
(nil)
在应用程序代码中,接收 (nil) 是确定数据缺失的标准方式,通常会触发缓存未命中,此时应用程序必须从主数据源(如数据库)获取数据,然后将其写回 Redis。
获取值的同时更改过期时间:GETEX
基本的 GET 命令只返回值。它不返回剩余的 TTL。如果你需要 TTL,请使用 TTL key 或 PTTL key 作为单独的命令。
GETEX 则不同:它返回值并同时更改键的过期时间。这对于滑动会话行为很有用,其中每次读取都会延长会话生命周期。
GETEX session:abc123 EX 1800
这会读取会话值并将过期时间重置为 30 分钟。不要随意将其用于正常的缓存读取,因为每次读取都变成了一个类似写入的操作,会更改键的元数据。
3. 实际应用:使用 GET 和 SET 进行缓存
GET 和 SET 的基本用例是实现简单的旁路缓存模式。
应用程序逻辑中的步骤:
- 尝试
GET product:500。 - 如果 Redis 返回一个值,解码并返回它。
- 如果 Redis 返回 nil,从主数据库获取产品。
- 使用
SET product:500 <json> EX 300存储序列化后的结果。 - 将结果返回给调用者。
这种模式可以减少数据库负载,但也会产生一个陈旧数据窗口。如果数据库中的产品发生了变化,Redis 可能仍然提供旧值,直到 TTL 过期或你的应用程序使该键失效。选择 TTL 时应基于数据允许的错误程度,而不仅仅是你想节省多少流量。
命名键而不制造混乱
Redis 不要求命名约定,但未来的你会需要。像 user:100:name、product:500:summary 或 rate:user:100:login 这样的可读模式使得使用 redis-cli 调试更加容易。
保持键清晰且合理简短。除非你在非常大的规模下运行并且已经测量了键的开销,否则通过将键命名为 u:100:n 来节省几个字节很少值得引起混淆。对于大多数团队来说,一致性比极端简洁更重要。
注意键中用户提供的值。如果电子邮件地址、URL 或租户名称成为键的一部分,请先对其进行规范化。否则,微小的格式差异可能会创建重复的缓存条目:
[email protected]
[email protected]
[email protected]
这些对于你的应用程序来说可能代表同一个用户,但对于 Redis 来说却是不同的键。
覆盖、空值和 Nil
SET 默认会覆盖:
SET config:mode "safe"
SET config:mode "fast"
GET config:mode
最终值是 "fast"。如果覆盖有风险,请使用 NX 或 XX。
另外,要区分缺失的键和空值。Redis nil 表示键缺失。空字符串是实际存储的值:
SET user:100:nickname ""
GET user:100:nickname
你的客户端库可能以不同方式表示这些:null、None、nil、空字节字符串或空字符串。请检查客户端行为,而不是猜测。
安全锁模式及警告
你经常会看到这种模式:
SET lock:invoice:123 "worker-7:1700000000" NX EX 30
这意味着“仅当此锁不存在时创建它,并在 30 秒后过期。”过期时间不是可选的。没有它,崩溃的工作进程可能会永久留下一个锁。
对于简单的单实例 Redis 设置,这种模式通常足以用于低风险的协调。对于跨故障、时钟漂移和多个 Redis 节点的关键分布式锁,请使用经过良好审查的库并理解其权衡。锁错误可能变成数据损坏错误。
使用 redis-cli 调试
当 GET 或 SET 路径行为异常时,直接检查键:
redis-cli GET product:500
redis-cli TTL product:500
redis-cli TYPE product:500
TYPE 很有用,因为 GET 只适用于字符串值。如果键持有哈希、列表、集合或有序集合,Redis 会返回类型错误。这通常意味着应用程序的两个部分正在为不同目的使用相同的键名。
如果你需要在开发期间检查几个相关的键,SCAN 比在繁忙的生产服务器上使用 KEYS 更安全:
redis-cli SCAN 0 MATCH 'product:500:*' COUNT 100
KEYS * 会在 Redis 扫描键空间时阻塞它。在小型本地实例上没问题。在生产环境中这是一个坏习惯。
在实际系统中选择 TTL
TTL 的选择是一个产品和运营决策,而不是 Redis 技巧。用户资料缓存可能容忍五分钟的陈旧。权限检查可能需要更短的 TTL 或显式失效。功能开关如果需要控制有风险的发布,可能需要近乎即时的更新。
以下是三种常见模式:
SET cache:product:500 "<json>" EX 300
SET session:abc123 "<json>" EX 1800
SET rate:user:100:login "1" EX 60 NX
产品缓存可以稍微陈旧。会话有明确的生存期。速率限制键使用 NX,以便第一次尝试创建窗口,后续尝试可以根据设计递增或检查相关键。
除非你有明确的失效路径,否则避免“永久缓存”键。永久缓存最终会成为第二个数据库,通常没有你对真实数据库应用的操作纪律。
序列化细节
Redis 字符串是二进制安全的。它们可以保存 JSON、MessagePack、压缩数据、计数器或纯文本。命令不关心这些。你的应用程序关心。
JSON 易于检查:
SET product:500 "{\"id\":500,\"name\":\"Desk Lamp\"}" EX 300
在某些应用程序中,二进制格式可以节省空间或 CPU,但它们会使终端调试更加困难。压缩有助于处理大型重复数据,但它也会增加 CPU 成本,并可能掩盖你正在缓存过大对象的事实。
对于计数器,如果多个客户端可能更新同一个键,请不要使用 GET 读取、在应用程序中添加、然后使用 SET 写入。请改用 Redis 原子计数器命令:
INCR page:view:500
EXPIRE page:view:500 86400
对于需要首次写入并设置过期时间的计数器,如果你需要严格的行为,请使用事务或小型 Lua 脚本。否则,请明确你接受的竞争条件。
避免键冲突
两个团队使用同一个 Redis 数据库可能会意外重用像 user:100 这样的键。一个团队使用 SET 存储 JSON;另一个团队使用 HSET 存储字段。下一个 GET 会返回类型错误,两个团队都会浪费时间。
命名空间有所帮助:
shop:prod:user:100:profile
shop:prod:session:abc123
billing:prod:invoice:9001
你不需要冗长的键,但要包含足够的上下文以避免环境、服务和数据类型之间的冲突。如果你跨应用程序共享 Redis,命名约定是接口的一部分。
何时不使用 GET 和 SET
字符串是起点,而不是整个 Redis 模型。如果你经常更新一个较大的 JSON 对象中的某个字段,哈希可能更清晰:
HSET user:100 name "Alice Johnson" email "[email protected]"
HGET user:100 email
如果你需要有序事件,请使用流或列表。如果你需要成员资格检查,请使用集合。如果你需要排名,请使用有序集合。为每个小改动重写整个序列化字符串起初很简单,但随着对象的增长,它可能会变得昂贵且笨拙。
发布前的小清单
在发布新的 GET 和 SET 路径之前,问几个简单的问题。
- 确切的键名是什么?
- 两个服务会意外使用同一个键吗?
- 键应该过期吗?
- 当 Redis 返回 nil 时会发生什么?
- 覆盖是否可以接受,还是应该使用
NX或XX? - 值是否足够小,可以作为单个字符串读写?
- 如果生产行为看起来不对,你能从
redis-cli调试该值吗?
这些问题能在大多数基本的 Redis 字符串错误变成事故之前捕获它们。命令语法很容易。键周围的生命周期才是错误通常隐藏的地方。
GET 和 SET 是小型命令,但具有很大的操作影响。为缓存数据使用过期时间,在覆盖重要时使用 NX 或 XX,将 nil 视为单独的状态,并保持键名足够一致,以便有人可以从终端调试它们。一旦字符串感觉局促,将相关字段移到哈希中,并使用与访问模式匹配的数据结构。