主体思路:
使用redis 键在多长时间后过期,(在过期时间点)通知订阅消费端执行定时邮件发送并更新发送结果。
redis的键过期事件也是一种 发布/订阅模型
- 修改redis配置文件,使其支持键事件过期通知
$ vi /usr/local/redis/etc/redis.conf
修改 notify-keyspace-events 的参数为 "Ex", 并重新启动redis
- 使用下面代码为订阅端, 检测key过期后的异步通知。
$redis = new Redis();
$redis->connect("127.0.0.1",6379);
$redis->auth("XXX");
$redis->setOption(Redis::OPT_READ_TIMEOUT,-1); // 使其守护进程不会出现超时错误
$redis->psubscribe(["__keyevent@0__:expired"],function(Redis $redis,string $pattern,string $channel,string $expireKey){
var_dump($pattern); // __keyevent@0__:expired
var_dump($channel); // __keyevent@0__:expired
var_dump($expireKey); // 返回 过期的 key,实际业务中,希望每一个定时邮件的key都是唯一的,
//否则正在执行的key业务,如果一个同样的key在执行时过期,则不会执行
// todo 业务代码继续...
});
- 具体实现步骤
3,1 . 发送定时邮件时,将要发送的定时邮件保存到mysql中,并在定时邮件发送表中创建是否已经发送的标识字段 is_send,该字段由redis键空间订阅端执行发送更新。
3,2 . 发送定时邮件时,使用php 操作redis设置一个过期key (重点说明:这个key必须要是唯一的过期key,该业务中过期的key,已经在订阅端执行过的,不能再使用),业务设计中最好带上上一步的mysql主键key 。
$redis->setex(string "delayMail:{$mysqlPriKeyId}",int 定时发送时间-time(),1);
3,3 . 由于在定时邮件未发送之前,用户可能会删除该定时邮件或修改定时邮件发送时间。此时需要调用php的redis删除原来的key,并重新设置key并设置新的过期时间
if($redis->exist("delayMail:{$mysqlPriKeyId}")){
$redis->del("delayMail:{$mysqlPriKeyId}");
// 如果用户是 修改定时邮件发送时间,则需要重新设置key
$redis->setex(string "delayMail:{$mysqlPriKeyId}",int 新的定时发送时间-time(),1);
}