明确缓存价格的标识与有效期

RedisBeginner
立即练习

介绍

一家小店将权威价格保存在准备好的主数据库中,用 Redis 记住顾客已经查看过的价格。重复查看应使用记住的值。但是,同一 SKU 在不同店铺或币种下可能有不同价格,而且记住的价格最终必须过期。

你将交付简短的 policy.py,选择完整缓存标识,并为每次填充设置有限有效期。你需要掌握基本终端导航和 Redis GET/SET。这里会讲解 Python 模板及每个需要修改的表达式;无需先修 Python 课程。主存储是已准备好的 SQLite 数据库,不是另一个数据库实现任务。本独立单元只使用 Terminal,并有自己的全新数据。

只编辑 /home/labex/project/cache-desk/policy.py。保留提供的工具、源表、全部源标识和价格,以及无关的商品目录备注。在三个函数中,只使用指定的源存储与缓存 API 及普通策略表达式;不要加入导入、shell 执行或无关资源。不要禁用缓存、大范围清空 Redis 或改变全局配置。我们测试受控的顺序请求,不保证生产延迟或强一致性。作者估计时长包含阅读和普通错误处理,尚未经初学者计时测试。

区分顾客实际请求的价格

本步骤中,你将观察真实缓存命中、发现键冲突,并让每个价格的完整标识成为键的一部分。

从 Terminal 的 shell 提示符开始。cd 改变当前目录;这个绝对路径在任意目录都有效。./desk 运行该目录中准备好的程序。下面每条命令都是单独的 shell 命令,不是 Redis 交互式提示符的输入。

cd /home/labex/project/cache-desk
./desk status

读取列顺序:shop / sku / currency / integer cents。North MUG USD 为 1200 分,South MUG USD 为 1500,North MUG EUR 为 1100,North FREE USD 为 0。状态显示读取权威价格,绝不填充缓存。

缓存未命中表示 Redis 没有请求键对应的值;缓存命中表示有值。预备应用使用旁路缓存(cache-aside):先读 Redis,只在未命中时读主存储,再记住结果。连续尝试两次同样的正常请求:

./desk view north MUG USD
./desk view north MUG USD

第一个结果应显示 "cents": 1200、"origin": "source"、"primary_selects": 1;第二个显示 "origin": "cache"、"primary_selects": 0。计数来自本请求的原生 SQLite SELECT 跟踪,不是估计的加速倍数。JSON 输出的字段名带引号;三个字段分别标识价格、来源和主存储读取次数。

现在请求另一店铺,并检查实际 Redis 键:

./desk view south MUG USD
./desk inspect south MUG USD
./desk policy

不完整的键只使用 MUG,可能为 South 返回 North 的 1200,尽管主存储仍保存 1500。inspect 读取键及其实际值,不会修复价格。提供的前缀 catalog 限定本课程键的范围。每个键必须以给定前缀及冒号开头,例如 catalog:。后缀必须区分全部三个标识字段,而非只有 SKU;接受等效的后缀编码。

模板使用 Python。def 定义函数,括号内名称是参数。缩进的函数体属于该函数。return 提供结果。identity 是按显示字段顺序排列的三项元组:identity[0] 是店铺,[1] 是 SKU,[2] 是币种。索引从零开始。f"...{name}..." 将表达式插入字符串;这里冒号是普通分隔符。课程标识只包含字母、数字、连字符和下划线,因此不会包含该分隔符。这种键格式不是任意用户文本的通用编码。

在 read_price 中,= 给变量赋值。cache.command("GET", key) 发送真实 Redis 命令。None 表示没有缓存值;if value is not None: 只在值存在时进入分支。这个明确判断保留零价格。Redis 返回字符串值,所以 int(value) 将其转换为整数分金额。return cents, "source" 中的逗号返回一对值。source.read(identity) 发出准备好的主存储 SELECT,并对未知标识抛出查找错误,没有后备参考价格。

保存使用完整键的文件。cat > policy.py 替换文件;带引号的 here-document 标记 'POLICY' 让 shell 按字面复制后续所有行,直到独占一行的 POLICY。复制整个代码块,保留四空格缩进。重复列出的函数保留预备读取行为;本步骤只改变键的选择。

cat > policy.py <<'POLICY'
def cache_key(prefix, identity):
    return f"{prefix}:{identity[0]}:{identity[1]}:{identity[2]}"


def read_price(source, cache, prefix, identity):
    key = cache_key(prefix, identity)
    value = cache.command("GET", key)
    if value is not None:
        return int(value), "cache"
    cents = source.read(identity)
    cache.command("SET", key, cents)
    return cents, "source"


def change_price(source, cache, prefix, identity, cents):
    source.write(identity, cents)
POLICY

每个标识查看两次:

./desk view north MUG USD
./desk view north MUG USD
./desk view south MUG USD
./desk view south MUG USD
./desk view north MUG EUR
./desk view north MUG EUR
./desk view north FREE USD
./desk view north FREE USD

每个新完整键的首次读取来自源存储,第二次来自 Redis。价格分别为 1200、1500、1100 和 0。命中了错误价格不算成功。旧的可丢弃键 catalog:MUG 不再使用;无需大范围删除。

如果输入 ./deskk,shell 会报告文件不存在。ls 列出实际工具;按正确拼写重新运行 ./desk。Python 报告语法或缩进错误时,显示 ./desk policy,再复制完整文件代码块,保留行首空格。不要重置 VM 或改变预备工具。此步骤检查在可丢弃测试环境中使用变化的标识,并保留实际主存储;它不要求某个特定键拼写。

为每次填充设置截止时间

本步骤中,你将在价格写入 Redis 时添加有限有效期,并防止缓存命中推迟截止时间。

继续使用 Terminal,工作目录为 /home/labex/project/cache-desk。检查一个已有完整键:

./desk inspect north MUG USD

pttl_ms 是以毫秒计的剩余有效期。-1 表示键没有过期时间,-2 表示键不存在。正值会倒计时。没有截止时间的值可能在外部价格改变后无限期保留。

Redis SET key value EX seconds 在一条命令中保存值及过期时间。本教学店铺选择 15 秒;可接受的新填充初始有效期必须为 12 到 20 秒。较短区间便于观察自然过期,不是生产 TTL 建议。只在未命中时设置截止时间。每次命中都续期,可能让热门旧价格无限期存活。普通 SET 覆盖一个有过期时间的键时,会清除原过期时间,除非明确要求保留。

使用相同的完整标识和设置过期时间的 SET 命令替换文件。Python 中,独立参数 "EX", 15 传给 Redis;15 是整数,不是带引号的标识符。

cat > policy.py <<'POLICY'
def cache_key(prefix, identity):
    return f"{prefix}:{identity[0]}:{identity[1]}:{identity[2]}"


def read_price(source, cache, prefix, identity):
    key = cache_key(prefix, identity)
    value = cache.command("GET", key)
    if value is not None:
        return int(value), "cache"
    cents = source.read(identity)
    cache.command("SET", key, cents, "EX", 15)
    return cents, "source"


def change_price(source, cache, prefix, identity, cents):
    source.write(identity, cents)
POLICY

上一步已填充的完整标识值仍没有过期时间:改代码不会追溯补上截止时间。forget 有意只删除一个已知标识的当前缓存键,不改变主存储价格或其他键。逐个移除这四个明确可丢弃的练习条目。这是学员的显式操作,绝不是大范围清空或读取工具的隐式修复。后续填充都会使用新截止时间。然后填充 North 的价格并检查:

./desk forget north MUG USD
./desk forget south MUG USD
./desk forget north MUG EUR
./desk forget north FREE USD
./desk view north MUG USD
./desk inspect north MUG USD
./desk view north MUG USD
./desk inspect north MUG USD

首次查看为 source/1;过期前再次查看为 cache/0。正 PTTL 会递减,而不是重回 15000。如果阅读超过 15 秒,实际未命中符合预期:无需改变策略,及时重复填充与检查命令即可。

你也可以通过提供的只读连接读取原生 Redis 值。./redis 将参数发送给本课程专用 Redis,而非通用 Redis 服务:

./redis GET catalog:north:MUG:USD
./redis TTL catalog:north:MUG:USD

这个命令行工具打印如 "1200" 的 JSON 字符串、原生整数 TTL,或代表缺失值的 null,绝不补取缺失价格。现在 sleep 16 要求 shell 等待真实的 16 秒,不模拟时间,也不替你删除键:

sleep 16
./desk inspect north MUG USD
./desk view north MUG USD
./desk view north MUG USD

重新填充前,inspect 显示 value: null 和 PTTL -2。下一次查看再次读取源存储,再下一次可以命中 Redis。本单元所有权威价格保持不变。评分器在隔离键上测试实际过期;阅读期间实际键可能自然过期,因此完成不依赖赶在短倒计时结束前操作。

总结

你在真实 Redis 键中区分了店铺、SKU 和币种,保留零价格,通过主存储读取验证缓存命中,并为每次填充设置不会随命中顺延的有效期。策略可以复用;已准备好的主存储和无关的商品目录数据保持完整。