基于Redis 订阅实现定时删除过期订单

Redis 的订阅|发布 功能,除了手动发布到 指定通道(这个可以实现在线聊天),还可以自动发布数据过期到系统默认通道,通过监听任务来实现监听数据过期

Posted by 昆山吴彦祖 on 2021.09.10

之前的订单过期自动删除,是通过队列延时任务执行的,这次尝试通过Redis的 订阅过期事件,理论上比队列任务效率更高,当然如果你队列通过swoole常驻内存实现,那当我没说。

还是基于laravel环境写的。不过这个不是必须的。



1、修改服务器redis配置redis.conf

notify-keyspace-events Ex


2、新增一个redis配置用于存储需要监听的数据

'redis' => [

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

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

        'default' => [
            '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_DB', '0'),
            'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
        ],

        'cache' => [
            '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'),
            'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
        ],
        
        'publisher' => [
            '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_DB', '2'),
            'read_write_timeout' => 0,
            'prefix'=>''
        ],

    ],

这里需要注意的prefix 前缀配置,因为我们系统是比较大的集群项目,所以redis存储都有前缀

但是 数据过期Redis自动发布的通道,不经过laravel系统,不会做前缀处理;但是laravel的监听会自动加前缀,导致无法监听到。如下图:


所以单独给publisher 配置了 prefix 为空。


3、写一个命令实现过期数据的订阅(监听)

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;

class Test extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'test';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '在5分钟后取消未付款订单。';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        ini_set('default_socket_timeout', -1);
        $redis=Redis::connection('publisher');//创建新的实例

        $redis->psubscribe(['__keyevent@*__:expired'], function ($message, $channel) {
            echo $channel.PHP_EOL;//订阅的频道
            
            echo $message.PHP_EOL;//过期的key
            
            // 这里就可以根据监听到的过期键值来删除数据库的无用数据了
        
        });
    }
}

如果不配置 ini_set('default_socket_timeout', -1); 而你用的是php的redis拓展,会发现命令执行到一定时间会报下面这个错误。这是因为php的redis拓展底层走的php的socket,会受到php配置文件中的socket连接时长限制。

RedisException 

  read error on connection to 127.0.0.1:****


4、最后把这个执行命令放到守护进程中,做常驻进程执行即可。


ps: Redis 不光可以监听事件,还可以监听特定键值,参考

redis