Laravel 实用小技巧——缓存标签的小秘密(下)
背景
在上篇文章《Laravel 实用小技巧——缓存标签的小秘密(上)》中,我们重点看了 Laravel 6 版本下,有关缓存标签在 Redis 方面的表现。本篇文章,我们来看一下 Laravel 11(11.13.0)版本下表现如何。
不知道是编排失误还是有意为之,Laravel 11 的官方网站中,有关缓存标签的部分居然删除了!!!不过代码中依旧可以正常使用,所以这并不会影响我们的测试。
Redis 表现
存储过程
首先,我们触发一条简单的带有缓存标签的缓存存储逻辑:
$md5Key = 'c4ca4238a0b923820dcc509a6f75849b';
Cache::tags(['md5_cache'])->put($md5Key, 'value');
Redis 的 MONITOR
命令监控如下:
1721030328.697946 [0 172.19.0.12:51492] "SELECT" "1"
1721030328.698334 [1 172.19.0.12:51492] "ZADD" "_database_tag:md5_cache:entries" "-1" "290cb8745b1f0ddb9afaaf05ba984c48288ffd1f:c4ca4238a0b923820dcc509a6f75849b"
1721030328.701448 [1 172.19.0.12:51492] "SET" "_database_290cb8745b1f0ddb9afaaf05ba984c48288ffd1f:c4ca4238a0b923820dcc509a6f75849b" "s:5:\"value\";"
从 Redis 操作命令可以看出,这里主要用到了两个 Key ,我们「大胆」猜测一下各自的作用,然后再根据后续的表现加以验证:
KEY 名称 | KEY 类型 | KEY 值 | 作用 | 简称 |
---|---|---|---|---|
_database_tag: md5_cache:entries | ZSET | 290cb8745b1f0ddb9afaaf05ba984c48288ffd1f: c4ca4238a0b923820dcc509a6f75849b | 缓存标签数据结构 | A |
_database_290cb8745b1f0ddb9afaaf05ba984c48288ffd1f: c4ca4238a0b923820dcc509a6f75849b | STRING | s: 5: “value”; | 存储标记了缓存标签的缓存内容 | B |
推理结论:
- 通过对比发现,Laravel 11 和 Laravel 6,在缓存标签的存储结构设计上,有着较大的不同。Laravel 11 仅通过一个 ZSET 结构,就存储了缓存标签的名称和缓存的 KEY;
- Laravel 11 使用 ZSET 的分值记录缓存对应的过期时间。同一份缓存数据,在 Laravel 6 版本中,会维护两份缓存标签数据(如果永久性缓存和临时性缓存同时设置的话),而在 Laravel 11 中仅维护一份数据,缓存标签中缓存的过期性通过分值区分。在 Laravel 6 中,需要在两份 SET 数据中获取,而在 Laravel 11 中,仅需要在一个 ZSET 数据中存储一份数据即可。如果需要筛选未设置过期的缓存 KEY,可以通过分值进行筛选;
- 与 Laravel 6 相同,Laravel 11 设置了多个缓存标签的同一份缓存数据,也会维护多个缓存 KEY。
我们可以进一步测试下:
$md5Key = 'c4ca4238a0b923820dcc509a6f75849b';
Cache::tags(['md5_cache'])->put($md5Key, 'value', 1000);
Cache::tags(['md5_cache'])->put($md5Key, 'value');
Redis 的 MONITOR
命令监控如下:
1721471422.872381 [1 127.0.0.1:40410] "ZADD" "_database_tag:md5_cache:entries" "1721472422" "290cb8745b1f0ddb9afaaf05ba984c48288ffd1f:c4ca4238a0b923820dcc509a6f75849b"
1721471422.876893 [1 127.0.0.1:40410] "SETEX" "_database_290cb8745b1f0ddb9afaaf05ba984c48288ffd1f:c4ca4238a0b923820dcc509a6f75849b" "1000" "s:5:\"value\";"
1721471422.878603 [1 127.0.0.1:40410] "ZADD" "_database_tag:md5_cache:entries" "-1" "290cb8745b1f0ddb9afaaf05ba984c48288ffd1f:c4ca4238a0b923820dcc509a6f75849b"
1721471422.878882 [1 127.0.0.1:40410] "SET" "_database_290cb8745b1f0ddb9afaaf05ba984c48288ffd1f:c4ca4238a0b923820dcc509a6f75849b" "s:5:\"value\";"
从 Redis 的命令可以看出,当同一个缓存 KEY 分别设置不同的过期时,ZSET 结构和 STRING 结构都是以覆盖的形式进行更新,这与 Laravel 6 的逻辑有所不同。
读取过程
读取带有缓存标签的缓存逻辑如下:
$md5Key = 'c4ca4238a0b923820dcc509a6f75849b';
$value = Cache::tags(['md5_cache'])->get($md5Key);
Redis 的 MONITOR
命令监控如下:
1721466729.401411 [0 127.0.0.1:38464] "SELECT" "1"
1721466729.402736 [1 127.0.0.1:38464] "GET" "_database_290cb8745b1f0ddb9afaaf05ba984c48288ffd1f:c4ca4238a0b923820dcc509a6f75849b"
Laravel 11 的缓存标签读取逻辑,相较于 Laravel 6,也做了调整。
现在少了一步从缓存标签 KEY 中读取缓存 KEY 的逻辑,这里直接可以得到缓存 KEY。缓存 KEY 中缓存标签部分应该是通过 哈希算法(缓存标签名称)
得到的。这比 Laravel 6 简洁了不少。
删除过程
删除带有缓存标签的缓存逻辑如下:
Cache::tags('md5_cache')->flush();
Redis 的 MONITOR
命令监控如下:
1721472120.371575 [1 127.0.0.1:33344] "ZSCAN" "_database_tag:md5_cache:entries" "0" "COUNT" "1000" "MATCH" "*"
1721472120.373301 [1 127.0.0.1:33344] "DEL" "_database_290cb8745b1f0ddb9afaaf05ba984c48288ffd1f:c4ca4238a0b923820dcc509a6f75849b"
1721472120.373597 [1 127.0.0.1:33344] "DEL" "_database_tag:md5_cache:entries"
通过监控命令发现,删除缓存标签的逻辑,相较于 Laravel 6 做了很大程度的优化:
- 使用
ZSCAN
命令进行迭代,性能比SMEMBERS
更优,虽说是全量扫描,但是采用游标迭代的方式进行获取数据,不会阻塞 Redis 线程; - 其他删除逻辑与 Laravel 6 并无大的差异;
- 经过测试发现,当缓存标签标记的缓存数量超过 1000 个时,在删除缓存标签时,会自动进行分组,每 1000 个缓存 KEY 执行一次
DEL
命令,这个 Laravel 6 的逻辑一样。
总结
我们通过 Laravel 6 和 Laravel 11 分别测试了缓存标签在 Redis 操作方面的表现,基本可以得到以下结论:
- Laravel 6 的缓存标签因为使用了
SMEMBERS
命令进行删除缓存标签,所以会存在阻塞 Redis 的隐患; - Laravel 11 缓存标签在 Redis 数据结构设计方面有较大改进,结构更精简,而且删除缓存时,使用的是
ZSCAN
命令,不会阻塞 Redis ,同时,由于结构精简,操作 Redis 的命令也少了很多; - 尽管缓存标签在分类管理缓存方面,设计得比较出色,但不得不说,如果放在之前提到的业务场景中(批量删除八十万无规则 KEY ),还是有些欠妥。为什么呢?因为这可能会导致 ZSET 结构发展成一个 BIGKEY 。
当然,以上结论仅仅是我们通过 Redis 的表现反向推测出来的,如果想了解更多底层细节的话,我们还需要去研究框架的底层代码,这将是我下一步的工作。
温馨提醒:
新技术的使用一定要慎重,要稳中求进,这不是保守,而是对线上业务负责的一种工作态度。另外,发现框架问题时,大家从多个角度去分析下问题,比如看看新版本的实现逻辑是否一致。因为框架本身也处于一个不断迭代升级的过程中,我们需要以发展的眼光来看待这些问题。解决问题永远比抱怨问题更有意义。
感谢大家的持续关注~
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: