There is no problem not worth solving, and no technology not worth learning!

In Laravel 6, the cache is deleted for no reason (cache::tags)

决定登录至 Redis 中确认一下,基于 Tinker 查询。按照如上的执行命令的顺序,以确认本地环境中是否存在对应的 key

1. The code for setting and obtaining the cache is as follows


    const TAG_THEME_EDITOR = 'theme_editor'; // 缓存标签:主题编辑

    const THEME_EDITOR_SESSION_PREFIX = 'theme_editor_session:'; // 缓存前缀:主题编辑会话

    const TTL = 86400; // 缓存的过期时间(秒)

    public static function saveSessionMetadata(string $sessionId, array $data): bool
    {
        return Cache::tags([
                ThemePreviewInterface::TAG_THEME_EDITOR,
                ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId]
            )
            ->put(ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX . $sessionId, json_encode($data), ThemePreviewInterface::TTL);
    }

    public static function getSessionMetadata(string $sessionId): array
    {
        $data = Cache::tags([
                ThemePreviewInterface::TAG_THEME_EDITOR,
                ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId
            ])
            ->get(ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX . $sessionId);
        if (empty($data)) {
            return [];
        }

        return JsonDecoder::jsonToArray($data);
    }


2. The expiration time (seconds) of the cache is 86400, but in the production environment, there is a very low probability that it will expire in less than 24 hours, and even the cache cannot be obtained in the past few hours. Print $DATA, its value is NULL.

3. Decide to eliminate the interference of external factors first, and start with the analysis of the program itself. It was initially suspected that the cache was operated somewhere in the program itself, resulting in the cache being cleared.

4. The cache related configuration in .env is as follows


CACHE_DRIVER=redis
REDIS_CLIENT=phpredis


5. Print the configuration item, database.reids.client=phpredis; cache.default=redis. as shown in Figure 1

打印配置项,database.reids.client=phpredis;cache.default=redis
Figure 1

/object # php artisan tinker
Psy Shell v0.9.12 (PHP 7.4.26 — cli) by Justin Hileman
>>> echo config('database.redis.client');
>>> echo config('database.redis.client');
phpredis⏎
>>> echo config('cache.default');
redis⏎
>>> 


6. The configuration in /config/cache.php is as follows


    'stores' => [

        'redis' => [
            'driver' => 'redis',
            'connection' => 'cache',
        ],



    ],



7. The configuration in /config/database.php is as follows



    'redis' => [

        'client' => env('REDIS_CLIENT', 'phpredis'),

        'options' => [
            'cluster' => env('REDIS_CLUSTER', 'predis'),
        ],

        'cache' => [
            'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => env('REDIS_CACHE_DB', 1),
            'persistent'=>env('REDIS_PERSISTENT', true),
            'persistent_id'=>env('REDIS_PERSISTENT_ID', 'cache'),
            'retry_interval'=>env('REDIS_PERSISTENT_RETRY', 3000),
        ],

    ],


8. In the alias part of the /config/app.php configuration file, the alias of the appearance of Illuminate\Support\Facades\Redis is not renamed. However, it does not throw an exception: please remove the redis facade alias in your “app” configuration file in order to avoid collsion with the php redis extension.. Decided to ignore this issue for the time being.


    'aliases' => [

        'Redis' => Illuminate\Support\Facades\Redis::class,
    ],


9. Still decide to rename to other content, such as RedisManager, to avoid conflicts with the Redis class.


    'aliases' => [

        'RedisManager' => Illuminate\Support\Facades\Redis::class,
    ],


10. Decide to log in to Redis to confirm, first confirm the order of the execution command in the local environment. as shown in Figure 2

决定登录至 Redis 中确认一下,先在本地环境确认一下执行命令的顺序
Figure 2

127.0.0.1:1>GET object_cache:tag:theme_editor_48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb:key
"s:22:"631ae7ba62b83808649247";"

127.0.0.1:1>SMEMBERS object_cache:631ae7ba62b83808649247:standard_ref
1) "object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:9734e008-f539-4f76-9e9f-d8037334b968:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb:product_detail"
2) "object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb"


127.0.0.1:1>GET object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb
"s:9329:"{"id":372}";"

127.0.0.1:1>


11. Decide to log in to Redis to confirm, and query based on Tinker. In the order of the above execution commands, to confirm whether there is a corresponding key in the local environment. as shown in Figure 3

决定登录至 Redis 中确认一下,基于 Tinker 查询。按照如上的执行命令的顺序,以确认本地环境中是否存在对应的 key
Figure 3

PS E:\wwwroot\object> php artisan tinker
Psy Shell v0.9.12 (PHP 7.4.27 — cli) by Justin Hileman
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->get('object_cache:tag:theme_
editor_48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb:key'));
string(30) "s:22:"631ae7ba62b83808649247";"
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->SMEMBERS('object_cache:631ae
7ba62b83808649247:standard_ref'));
array(2) {
  [0]=>
  string(186) "object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:9734e008-f539-4f76-9e9f-d8037334b968:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb:product_detail"
  [1]=>
  string(134) "object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb"
}
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->GET('object_cache:c613daaf6b
394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb'));
string(9339) "s:9329:"{"id":372}";"
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->TTL('object_cache:c613daaf6b
394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb'));
int(81060)
=> null
>>>                                                                                                                         


12. TTL key, in seconds, return the remaining life time (TTL, time to live) of the given key. A key is generated at 2022/09/09 15:14. The current time is 2022/09/09 16:44 . 1 hour 30 minutes has passed. 86400 – 1 * 3600 – 30 * 60 = 81000. The remaining number of seconds to survive is: int (81060). in line with expectations. as shown in Figure 4

TTL key,以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。一个 key 是 2022/09/09 15:14 生成的。现在时间是 2022/09/09 16:44 。已经过去了 1 小时 30 分钟。86400 - 1 * 3600 - 30 * 60 = 81000。剩余生存秒数为:int(81060)。符合预期
Figure 4

13. Decide to regenerate one online environment, and then check the number of seconds for the remaining survival time. The remaining number of survival seconds for a just generated key is: int (85730). in line with expectations. as shown in Figure 5

决定在线上环境重新生成一个,然后查看剩余生存时间秒数。刚生成的一个 key 的剩余生存秒数为:int(85730)。符合预期
Figure 5

14. Decide to log in to Redis to confirm, and query based on Tinker. In the order of the above execution commands, to confirm whether there is a corresponding key in the online environment. Make sure the corresponding key is not completely emptied. If cache::tags(themepreviewInterface::tag_theme_editor is executed )._. $sessionId)->flush() or cache::flush(), then execute the query var_dump(\illuminate\support\facades\redis::connection(cache)->get(arrest9_cache:tag:theme_editor_uq6blxo7gsbwhqovcxqashx8sdas6bxh2lwe:key)); null will return null, but now there is a value. As shown in Figure 6, Figure 6.1

决定登录至 Redis 中确认一下,基于 Tinker 查询。按照如上的执行命令的顺序,以确认线上环境中是否存在对应的 key。确定对应的 key 未被完全清空。
Figure 6

 

如果执行了 Cache::tags(ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId)->flush() 或者 Cache::flush(),则 执行查询 var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->get('arrest9_cache:tag:theme_editor_Uq6BLXO7GsbWHQovCXQAshX8SdaS6BXh2lWE:key')); 时,会返回 null,但是现在是有值的
Figure 6.1

>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->get('arrest9_cache:tag:theme_editor_Uq6BLXO7GsbWHQovCXQAshX8SdaS6BXh2lWE:key'));
string(30) "s:22:"63243db5c01db958165221";
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->TTL('arrest9_cache:tag:theme_editor_Uq6BLXO7GsbWHQovCXQAshX8SdaS6BXh2lWE:key'));
int(-1)
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->SMEMBERS('arrest9_cache:63243db5c01db958165221:standard_ref'));
array(0) {
}
=> null
>>> 



>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->get('object_cache:tag:theme_
editor_ERo5GoALUnDuxcMtW3yPYPKsjwci8DVZR2ij:key'));
NULL
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->SMEMBERS('object_cache:632a7
fda56e94065934346:standard_ref'));
array(0) {
}
=> null
>>>  


15. Decide to add corresponding logs based on event monitoring. Create a new listener file /app/listeners/cache/logKeyWrittenListener.php , when the key is written.


key, ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX) !== false) {
            Log::debug('theme_editor_session_key_written', [
                'key' => $event->key,
                'tags' => $event->tags,
                'value' => $event->value,
                'seconds' => $event->seconds
            ]);
        }
    }
}



16. The generated log information is as follows. as shown in Figure 7

生成的日志信息如下
Figure 7

[2022-09-15 17:26:05] local.DEBUG: theme_editor_session_key_written {
    "key": "theme_editor_session:7JivgR1WqXVeE4hsyA335ILc60k153yTynIN",
    "tags": [
        "theme_editor",
        "theme_editor_7JivgR1WqXVeE4hsyA335ILc60k153yTynIN"
    ],
    "value": "",
    "seconds": 86400
} 


17. Create a new listener file /app/listeners/cache/logCacheHitListener.php , when the key is hit.


    /**
     * 处理事件
     *
     * @param  CacheHit  $event
     * @return void
     */
    public function handle(CacheHit $event)
    {
        if (config('app.debug') === true && strpos($event->key, ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX) !== false) {
            Log::debug('theme_editor_session_cache_hit', [
                'key' => $event->key,
                'tags' => $event->tags,
                'value' => $event->value
            ]);
        }
    }



[2022-09-15 18:09:08] local.DEBUG: theme_editor_session_cache_hit {
    "key": "theme_editor_session:KrV0Nf5rMOiS5ZdF6i2uDandZZZWPPpXcQ4D",
    "tags": [
        "theme_editor",
        "theme_editor_KrV0Nf5rMOiS5ZdF6i2uDandZZZWPPpXcQ4D"
    ],
    "value": ""
} 


18. Create a new listener file /app/listeners/cache/logCacheMissedListener.php , when the key is missed.


    /**
     * 处理事件
     *
     * @param  CacheMissed  $event
     * @return void
     */
    public function handle(CacheMissed $event)
    {
        if (config('app.debug') === true && strpos($event->key, ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX) !== false) {
            Log::debug('theme_editor_session_cache_missed', [
                'key' => $event->key,
                'tags' => $event->tags
            ]);
        }
    }



[2022-09-15 18:10:15] local.DEBUG: theme_editor_session_cache_missed {
    "key": "theme_editor_session:KrV0Nf5rMOiS5ZdF6i2uDandZZZWPPpXcQ4D",
    "tags": [
        "theme_editor",
        "theme_editor_KrV0Nf5rMOiS5ZdF6i2uDandZZZWPPpXcQ4D"
    ]
} 


19. However, when removing cached data with tags, use cache::tags(ThemepreviewInterface::tag_theme_editor ._. $SESSIONID)->flush(); , the listener /app/listeners/cache/logKeyForgottenListener.php does not generate logs, when clearing the key time.


    /**
     * 处理事件
     *
     * @param  KeyForgotten  $event
     * @return void
     */
    public function handle(KeyForgotten $event)
    {
        if (config('app.debug') === true && strpos($event->key, ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX) !== false) {
            Log::debug('theme_editor_session_key_forgotten', [
                'key' => $event->key,
                'tags' => $event->tags
            ]);
        }
    }


20. Decided to use the flush method to clear all caches, cache::flush();. to confirm whether the event to clear the key will be triggered, and confirm that it has not been triggered. But when all cache operations are cleared, the cached data with tags can no longer be obtained. All cached data is emptied, regardless of whether it is labeled or not. as shown in Figure 8

决定使用 flush 方法清空所有的缓存,Cache::flush();。以确认是否会触发清除 key 的事件,确认未触发。但是当执行了清空所有的缓存操作后,已经无法获取带有标签的缓存数据了。不论是否带有标签,所有的缓存数据皆被清空
8

21. So even if there are 4 cache log listeners, it is still difficult to judge when the key is cleared. You can only record the corresponding logs in the places that are expected to be cleared first.

22. For the 16th step, I decided to consult my operation and maintenance colleagues. Sometimes the operation and maintenance will run the python script for the dead key of Redis, and manually perform the cleaning operation. Therefore, it is decided to simulate and reproduce the same python script in the local environment. Execute: python batch-remove-rediskey.py /tmp. The locality has not been reproduced, and the analysis script may be reproduced online. However, the operation and maintenance has been determined, and this script has not been run manually during the recent problem period. as shown in Figure 9

针对第 16 步骤,决定咨询一下运维同事,有时候运维会针对 Redis 的 死 key ,运行 Python 脚本,手动执行清理操作。因此,决定将同样的 Python 脚本在本地环境模拟复现。执行:python batch-remove-rediskey.py /tmp。本地未复现,分析脚本,线上有可能复现。不过,运维已经确定,在最近出现问题的时间段内,并未手动运行过此脚本
Figure 9

PS E:\wwwroot\object> python batch-remove-rediskey.py /tmp
/tmp*
All done


23. Since 2 tags are set, the number of keys under one tag will continue to increase, and the number may eventually reach several thousand or even tens of thousands in a week. The final decision is to set only 1 tag. The code to set and get the cache is as follows


    const TAG_THEME_EDITOR = 'theme_editor'; // 缓存标签:主题编辑

    const THEME_EDITOR_SESSION_PREFIX = 'theme_editor_session:'; // 缓存前缀:主题编辑会话

    const TTL = 86400; // 缓存的过期时间(秒)

    public static function saveSessionMetadata(string $sessionId, array $data): bool
    {
        return Cache::tags([
                ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId]
            )
            ->put(ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX . $sessionId, json_encode($data), ThemePreviewInterface::TTL);
    }

    public static function getSessionMetadata(string $sessionId): array
    {
        $data = Cache::tags([
                ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId
            ])
            ->get(ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX . $sessionId);
        if (empty($data)) {
            return [];
        }

        return JsonDecoder::jsonToArray($data);
    }


24. After only 1 tag is set, and the number of keys in this tag is generally maintained at about 20, the cache cannot be hit, and the overall operation is normal. The preliminary analysis concluded that when using cache::tags, the number of tags under it should not be too much. It is recommended to set only 1, and the number of keys under each tag should not be too much. It is recommended not to exceed. 100.

PHP / Laravel / Yii2 Legacy Project Maintenance & Long-Term Technical Support

If your PHP / Laravel / Yii2 project is already in production but needs bug fixing, API troubleshooting, performance optimization, developer handover support, or long-term maintenance, feel free to contact me for remote technical support.

Ideal For:
✅ PHP legacy systems without active maintenance
✅ Laravel / Yii2 project bug fixes
✅ Admin panel feature iterations
✅ RESTful API troubleshooting
✅ MySQL / Redis / Nginx performance issues
✅ Long-term remote part-time maintenance

We can start with a small task:
✅ Production error troubleshooting
✅ API issue analysis
✅ Slow query and performance bottleneck diagnosis
✅ Initial code structure review
✅ Deployment environment and log inspection

If you would like to discuss your project, please contact me and mention: PHP Maintenance Consultation.

Contact Me:
Telegram: @shuijingwan
WeChat: 13980074657
Email: shuijingwanwq@gmail.com

评论

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.