我在 Laravel 10 中有一个应用程序,它允许对候选人进行投票,并显示按投票排序的这些候选人的列表。该候选列表是从数据库中获取的,进行一些计算,然后使用适当的标签保存到 Redis 缓存中。 (实际上是几个标签:用户列表、面板列表等。但这里与此无关,因为也仅一个标签存在问题)。
用户投票给候选人后,它会刷新所有标签数据。另一个列表请求从数据库中获取所有数据并将其保存到 Redis 缓存(已标记)。 一切看起来都正常 - 缓存的数据已正确刷新,用户始终看到最新的数据。如果目前没有人投票,则会从缓存中获取数据,从而加快请求速度。
Laravel 10 处理标记缓存的方式:
Cache::tags(['object-1'])->put('cand-votes', $votes, $TTL);
在 Redis 中创建 2 个密钥:
正常情况(低流量):
keys app_cache*
1) "app_cache:tag:object-1:entries"
2) "app_cache:6461bc79646c190a1c986d8ca1ab917e8ddf0d8f:cand-votes"
在 app_cache:tag:object-1:entries 中有:
zscan app_cache:tag:object-1:entries 0
1) "0"
2) 1) "6461bc79646c190a1c986d8ca1ab917e8ddf0d8f:cand-votes"
2) "1689766717"
因此我们知道该缓存标签内有哪些缓存键及其 TTL。拿到钥匙时:
get app_cache:6461bc79646c190a1c986d8ca1ab917e8ddf0d8f:cand-votes
"150"
存储的值有效。
当每秒大约有 15 个投票或更多(因此每秒有 15 个 Redis 刷新请求)时,就会出现问题。 在某些时候,存储在 Redis 中的标签会变得……损坏,并且如果不使用 arisan cache:clear 命令刷新整个 Redis,就无法刷新数据。
无效行为(高流量,15个用户8秒内投票150次):
keys app_cache*
1) "app_cache:6461bc79646c190a1c986d8ca1ab917e8ddf0d8f:cand-votes"
get app_cache:6461bc79646c190a1c986d8ca1ab917e8ddf0d8f:cand-votes
"38"
只有带有数据的缓存键(已过时)。
条目键以及缓存键的排序列表消失了
现在在应用程序内,调用:
Cache::tags(['object-1'])->get('cand-votes');
总是返回错误的数据(本例中为 38)。但刷新缓存:
Cache::tags(['object-1'])->flush();
无法刷新该密钥(因为条目密钥已消失)。因此,我在 TTL 周期内陷入了无效数据的困境,无法用另一个请求刷新它。只有使用 artisan cache:clear 命令清除整个缓存才有帮助。
总体而言,缓存放置命令似乎创建了最近删除的密钥,并且条目密钥随后很快被删除。
我想到的一些解决方法:
这些都不理想,我认为这是一种“黑客”。
有人使用标记缓存遇到过这样的问题吗? 寻找有关如何处理涉及刷新缓存的并发情况的任何建议。
是的,我们在高负载下的平台上遇到了同样的问题,在队列中创建和刷新缓存标签。
我们关闭了高负载查询的缓存(在确保其索引正确之后)。
有趣的是,最新的 Laravel 文档说不要在 redis 中使用缓存标签。我们要将缓存移至 Memcached。