操作 Redis 的数据结构
目标
按各自的用途使用 Redis 的六种数据结构,并亲手练熟生产环境中绝对不能用的命令及其替代方案。
为什么重要
只把 String 当作 Redis 用的代码到处都是:全部序列化成 JSON 存进去、再取出来。这样能跑,但哪怕只想改一个字段,也得读出整体、解析、再写回去,而如果两个请求同时这样做,其中一个就会悄悄地把另一个覆盖掉。用 Hash 的话,单个字段就能以原子方式修改。光是选对一种数据结构,就能消除一个竞争条件。此外,这个实验还有一点必须刻在脑子里——Redis 以单线程处理命令,所以一个 O(N) 的命令就会造成全面停顿。在拥有数百万个键的实例上,一次 KEYS * 就会让所有请求停上好几秒。第 7 步中的 SCAN 就是为此而存在的。
步骤
- 把
redis-cli PING的结果保存到/root/rd/ping.txt。 - 让
app:hits增加 3 次。值必须是3。 - 在
user:42这个 Hash 中放入name、email、plan三个字段。HLEN user:42的结果是 3。 - 往
feed:42这个 List 中放入 12 条动态,并裁剪成只保留最近的 10 条。LLEN feed:42的结果是 10。 - 创建
tag:redis和tag:cache两个集合,把交集保存到/root/rd/inter.txt。交集必须有 2 个元素。 - 往
score:game1这个有序集合中放入 5 个带分数的成员。ZCARD的结果是 5,ZSCORE score:game1 p3的结果是30。 - 用
/root/rd/scan.sh通过 SCAN 找出所有匹配user:*模式的键,写入/root/rd/scan.out。脚本中不能出现KEYS字符串。 - 在
/root/rd/mem.md中创建一个 Markdown 表格。行标题是string、hash、list、set、zset五个,在bytes列中写入MEMORY USAGE的结果。
参考
- 裁剪 List:
LTRIM feed:42 0 9 - 交集:
SINTER tag:redis tag:cache - SCAN 循环:从游标 0 开始,重复执行,直到返回的游标变回 0
- 常见错误 1:使用
KEYS *——在开发机上很快,在生产环境里就是故障。 - 常见错误 2:用
DEL删除巨大的键——UNLINK会在后台回收。
确认连接
把 redis-cli PING 的结果保存到 /root/rd/ping.txt。
redis-cli 不带参数运行时是交互式的。在脚本中,把命令作为参数传入更好。
用 String 做计数器
让 app:hits 增加 3 次。值必须是 3。
递增命令即使键不存在,也会从 0 开始。它是原子的,这正是这种数据结构能做计数器的原因。
用 Hash 存储对象
在 user:42 这个 Hash 中放入 name、email、plan 三个字段。HLEN user:42 的结果是 3。
按字段存入和取出。想想这与把 JSON 整体存进去有什么不同。
用 List 保留最近的动态
往 feed:42 这个 List 中放入 12 条动态,并裁剪成只保留最近的 10 条。LLEN feed:42 的结果是 10。
新条目从前面放入,并把列表裁剪掉,使它不会变长。裁剪有专门的命令。
用 Set 求标签交集
创建 tag:redis 和 tag:cache 两个集合,把交集保存到 /root/rd/inter.txt。交集必须有 2 个元素。
有专门的命令用来求两个集合的共同元素。不要在应用里用循环去做。
用 ZSet 管理分数
往 score:game1 这个有序集合中放入 5 个带分数的成员。ZCARD 的结果是 5,ZSCORE score:game1 p3 的结果是 30。
分数和成员一起放入。放入之后,可以只查询某个成员的分数。
用 SCAN 代替 KEYS 来遍历
用 /root/rd/scan.sh 通过 SCAN 找出所有匹配 user:* 模式的键,写入 /root/rd/scan.out。脚本中不能出现 KEYS 字符串。
重复执行,直到游标回到 0。也有模式匹配的选项。
制作各数据结构的内存对比表
在 /root/rd/mem.md 中创建一个 Markdown 表格。行标题是 string、hash、list、set、zset 五个,在 bytes 列中写入 MEMORY USAGE 的结果。
把同样的数据用不同的结构存储,测出实际用量。有命令可以告诉你每个键的用量。