前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Laravel 7.x 使用 keyspace notification 监听不到 Key 过期事件

Laravel 7.x 使用 keyspace notification 监听不到 Key 过期事件

作者头像
hedeqiang
发布2020-08-31 11:16:31
1.7K0
发布2020-08-31 11:16:31
举报
文章被收录于专栏:LaravelCode

场景使用:假设有一个订单 30 分钟以后未付款 自动关闭该订单。或者会员到期自动提醒续费等等。

这个在 Laravel 中其实有更好的选择方式 队列,使用延时队列

代码语言:javascript
复制
ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(30));

但是我还想到一种方案就是使用 Redis 的键空间通知(keyspace notification)。意思就是当 Redis 的 key 删除是,回主动通知发送消息给我们,我们只需要监听订阅对应的事件即可。

接下来我还原事情经过。说一下我遇到的问题,以及最后是如何解决的。

首先 Redis 的 keyspace notification 默认是不开启的。我们需要主动开启,开启方式如下 修改 redis.conf 配置文件 找到 notify-keyspace-events "" 默认是空字符串,表示未开启.

代码语言:javascript
复制
notify-keyspace-events "Ex"

然后重启 Redis

或者直接 redis-cli 下输入如下命令进行修改

代码语言:javascript
复制
redis-cli config set notify-keyspace-events Ex

具体 Ex 代表啥意思 参考如下表格:

字符

发送的通知

K

键空间通知,所有通知以 keyspace@<db> 为前缀

E

键事件通知,所有通知以 keyevent@<db> 为前缀

g

DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知

$

字符串命令的通知

l

列表命令的通知

s

集合命令的通知

h

哈希命令的通知

z

有序集合命令的通知

x

过期事件:每当有过期键被删除时发送

e

驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送

A

参数 g$lshzxe 的别名

好了,言归正传,接下来在 Laravel 中,我是新建一个 Command 命令

代码语言:javascript
复制
php artisan make:command OrderExpire

内容如下:

代码语言:javascript
复制
<?php

namespace App\Console\Commands;

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

class OrderExpire extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'order:expire';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '处理订单失效';

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

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $redis = Redis::connection('publisher');
        Redis::subscribe(['__keyevent@0__:expired'], function ($message, $channel) {
            // 处理订单失效 逻辑
            echo '订单已失效';
        });
    }
}

configdatabase.php 中 redis 配置里添加 如下内容:

代码语言:javascript
复制
'publisher' => [
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'password' => env('REDIS_PASSWORD', null),
    'port' => env('REDIS_PORT', 6379),
    'database' => 0,
    'read_timeout' => 0,
    'persistent' => true, 
    'read_write_timeout' => 0,
],

在 路由文件下 编写 Redis 键 到期命令,设置 5 秒失效

代码语言:javascript
复制
use Illuminate\Support\Facades\Redis;
Route::get('/', function () {
    Redis::setex('order_2000123421',5,'2000123421');
});

接下来运行行项目

我们首先在 Laravel 项目中运行 控制台命令

代码语言:javascript
复制
php artisan order:expire

接下来在 red-cli 中也监听过期命令

代码语言:javascript
复制
redis-cli
127.0.0.1:6379> psubscribe __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"

访问路由文件,设置 Redis Key,但是你会发现实际上我们编写的控制台命令,Redis 5 秒过后并不会触发任何事件。而 redis-cli 5秒以后会监听到。

下面是 redis-cli 的结果:

下图是 Laravel 项目的结果,过一定事件还会出现连接错误:

这个问题纠结了挺久。最后解决方案如下:

解决方案

不使用 Laravel 自带的 Redis 门面,改为原生 Redis 。修改 handle 方法

代码语言:javascript
复制
$redis = new \Redis();
$redis->connect('redis', '6379');
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
$pattern = '__keyevent@0__:expired';
$redis->psubscribe([$pattern], function ($message, $channel)
{
    echo '订单已失效';
});

接下来再次重启命令,访问路由就可以看到自己想要的内容了

Why

I don't know。。。

我猜是 Laravel 的门面 Illuminate\Support\Facades\Redis ,这个订阅可能需要配合 发布一起使用。但是我不清楚这个事件订阅如何起作用,总不能直接发布 Redis::publish('test-channel', json_encode(['foo' => 'bar'])); 吧?希望遇到的童鞋能和我交流一下。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 解决方案
  • Why
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档