自学内容网 自学内容网

Laravel 实用小技巧——缓存标签的小秘密(下)

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 值作用简称
laravel_database_laravel_cache: tag: md5_cache: keySTRINGs: 22: “669491143c3b9110447653”;存储缓存标签序列化标识A
laravel_database_laravel_cache: 669491143c3b9110447653: forever_refSETlaravel_cache: e3bcc880c7090c3d28cec29cfa43354dc03a9a0b: c4ca4238a0b923820dcc509a6f75849b存储标记了缓存标签的缓存 KEY 名称B
laravel_database_laravel_cache: e3bcc880c7090c3d28cec29cfa43354dc03a9a0b: c4ca4238a0b923820dcc509a6f75849bSTRINGs: 5: “value”;存储具体的缓存内容C
_database_tag: md5_cache:entriesZSET290cb8745b1f0ddb9afaaf05ba984c48288ffd1f: c4ca4238a0b923820dcc509a6f75849b缓存标签数据结构A
_database_290cb8745b1f0ddb9afaaf05ba984c48288ffd1f: c4ca4238a0b923820dcc509a6f75849bSTRINGs: 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 的表现反向推测出来的,如果想了解更多底层细节的话,我们还需要去研究框架的底层代码,这将是我下一步的工作。

温馨提醒:
新技术的使用一定要慎重,要稳中求进,这不是保守,而是对线上业务负责的一种工作态度。另外,发现框架问题时,大家从多个角度去分析下问题,比如看看新版本的实现逻辑是否一致。因为框架本身也处于一个不断迭代升级的过程中,我们需要以发展的眼光来看待这些问题。

解决问题永远比抱怨问题更有意义。


原文地址:https://blog.csdn.net/m0_73464010/article/details/140675339

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!