本文所说的定时任务或者说计划任务并非不少人想象中的那样,好比说天天凌晨三点自动运行起来跑一个脚本。这种都已经烂大街了,随便一个 Crontab 就能搞定了。php
这里所说的定时任务能够说是计时器任务,好比说用户触发了某个动做,那么从这个点开始过二十四小时咱们要对这个动做作点什么。那么若是有 1000 个用户触发了这个动做,就会有 1000 个定时任务。因而这就不是 Cron 范畴里面的内容了。linux
举个最简单的例子,一个用户推荐了另外一个用户,咱们定一个二十四小时以后的任务,看看被推荐的用户有没有来注册,若是没注册就给他搞一条短信过去。git
在 Redis 的 2.8.0 版本以后,其推出了一个新的特性——键空间消息(Redis Keyspace Notifications),它配合 2.0.0 版本以后的 SUBSCRIBE 就能完成这个定时任务github
的操做了,不过定时的单位是秒。redis
(1)Publish / Subscribe bash
Redis 在 2.0.0 以后推出了 Pub / Sub 的指令,大体就是说一边给 Redis 的特定频道发送消息,另外一边从 Redis 的特定频道取值——造成了一个简易的消息队列。服务器
(2)Redis Keyspace Notificationsapp
在 Redis 里面有一些事件,好比键到期、键被删除等。而后咱们能够经过配置一些东西来让 Redis 一旦触发这些事件的时候就往特定的 Channel 推一条消息。函数
大体的流程就是咱们给 Redis 的某一个 db 设置过时事件,使其键一旦过时就会往特定频道推消息,我在本身的客户端这边就一直消费这个频道就行了。ui
之后一来一条定时任务,咱们就把这个任务状态压缩成一个键,而且过时时间为距这个任务执行的时间差。那么当键一旦到期,就到了任务该执行的时间,Redis 天然会把过时消息推去,咱们的客户端就能接收到了。这样一来就起到了定时任务的做用。
这里须要配置 notify-keyspace-events 的参数为 “Ex”。x 表明了过时事件。notify-keyspace-events "Ex" 保存配置后,重启Redis服务,使配置生效。
重启Reids服务器:
root@iZ23s8agtagZ:/etc/redis# service redis-server restart redis.conf
Stopping redis-server: redis-server. Starting redis-server: redis-server.
添加过时事件订阅 开启一个终端,redis-cli 进入 redis 。开始订阅全部操做,等待接收消息。
tinywan@iZ23a7607jaZ:~$ redis-cli -h 127.0.01.4 -p 63789
127.0.0.1:63789> psubscribe __keyevent@0__:expired Reading messages... (press Ctrl-C to quit) 1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
再开启一个终端,redis-cli 进入 redis,新增一个 20秒过时的键:
1270.01.1.1:63789> SETEX coolName 123 20 OK 121.41.188.109:63789> get coolName "20"
121.41.188.109:63789> ttl coolName (integer) 104
另一边执行了阻塞订阅操做后的终端,20秒过时后有以下信息输出:
121.141.188.209:63789> 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"
4) "coolName"
说明:说明对过时Key信息的订阅是成功的。
Redis实例化类:(RedisInstance.class.php)
<?php class RedisInstance { private $redis; public function __construct($host = '121.41.88.209', $port = 63789) { $this->redis = new Redis(); $this->redis->connect($host, $port); } public function expire($key = null, $time = 0) { return $this->redis->expire($key, $time); } public function psubscribe($patterns = array(), $callback) { $this->redis->psubscribe($patterns, $callback); } public function setOption() { $this->redis->setOption(\Redis::OPT_READ_TIMEOUT,-1); } }
过时事件的订阅:(psubscribe.php)
<?php require_once './RedisInstance.class.php'; $redis = new \RedisInstance();
// 解决Redis客户端订阅时候超时状况 $redis->setOption(); $redis->psubscribe(array('__keyevent@0__:expired'), 'psCallback'); // 回调函数,这里写处理逻辑
function psCallback($redis, $pattern, $chan, $msg) { echo "Pattern: $pattern\n"; echo "Channel: $chan\n"; echo "Payload: $msg\n\n"; }
说明:psCallback 函数为订阅事件后的回调函数。$redis, $pattern, $chan, $msg 四个参数为回调时返回的参数。 详细说明见下面 Redis 官方文档对 psubscribe 的说明。
由于订阅事件启动后是阻塞执行的,因此咱们尝试在终端执行 psubscribe.php 这个脚本。
D:\wamp\www\redistest>php psubscribe.php
设置一个过时事件:
121.41.188.109:63789> SETEX username123 20
20秒过时时间到时,另一边执行了脚本被阻塞的终端,有以下信息输出订阅结果:
D:\wamp\www\redistest>php psubscribe.php Pattern: __keyevent@0__:expired Channel: __keyevent@0__:expired Payload: username123
以上PHP操做Reids是成功的
有个问题 作到这一步,利用 phpredis 扩展,成功在代码里实现对过时 Key 的监听,并在 psCallback()里进行回调处理。 开头提出的两个需求已经实现。 但是这里有个问题:redis 在执行完订阅操做后,终端进入阻塞状态,须要一直挂在那。且此订阅脚本须要人为在命令行执行,不符合实际需求。
实际上,咱们对过时监听回调的需求,是但愿它像守护进程同样,在后台运行,当有过时事件的消息时,触发回调函数。 使监听后台始终运行 但愿像守护进程同样在后台同样,
我是这样实现的。
Linux中有一个nohup命令。功能就是不挂断地运行命令。 同时nohup把脚本程序的全部输出,都放到当前目录的nohup.out文件中,若是文件不可写,则放到<用户主目录>/nohup.out 文件中。那么有了这个命令之后,无论咱们终端窗口是否关闭,都可以让咱们的php脚本一直运行。
编写PHP脚本文件:
<?php #! /usr/local/php/bin/php
require_once './redis.class.php'; $redis = new MyRedis(); $redis->setOption(); $redis->psubscribe(array('__keyevent@0__:expired'), 'psCallback'); // 回调函数,这里写处理逻辑
function psCallback($redis, $pattern, $chan, $msg) { echo "Pattern: $pattern\n"; echo "Channel: $chan\n"; echo "Payload: $msg\n\n"; }
注意:不过咱们在开头,须要申明 php 编译器的路径:#! /usr/local/php/bin/php 。 这是执行 php 脚本所必须的。
而后,nohup 不挂起执行 nohupRedisNotify.php,注意 末尾的 &
[root@chokingwin HiGirl]# nohup ./nohupRedisNotify.php & [1] 4456 nohup: ignoring input and appending output to `nohup.out'
确认一下脚本是否已在后台运行。查看进程结果以下:
[root@chokingwin HiGirl]# ps PID TTY TIME CMD 3943 pts/2 00:00:00 bash 4456 pts/2 00:00:00 nohupRedisNotif 4480 pts/2 00:00:00
说明:脚本确实已经在 4456 号进程上跑起来。
最后在查看下nohup.out cat 一下 nohuo.out,看下是否有过时输出:
[root@chokingwin HiGirl]# cat nohup.out [root@chokingwin HiGirl]#
并无。咱们仍是老样子,新增一个10秒过时的的键 name。10秒后,咱们再 cat 一次。
[root@chokingwin HiGirl]# cat nohup.out Pattern: __keyevent@0__:expired Channel: __keyevent@0__:expired Payload: name
说明监听过时事件并回调成功。
nohup没有输出的状况:
sudo nohup nohupRedisNotify.php > /dev/null 2>&1 &
查看jobs进程ID:[ jobs -l ]命令
www@iZ232eoxo41Z:~/tinywan $ jobs -l [1]- 1365 Stopped (tty output) sudo nohup nohupRedisNotify.php > /dev/null 2>&1 [2]+ 1370 Stopped (tty output) sudo nohup nohupRedisNotify.php > /dev/null 2>&1
linux kill进程杀不掉(解决办法):
kill -9 PID
www@iZ232eoxo41Z:~/tinywan $ sudo kill -9 1370 [2]+ Killed sudo nohup nohupRedisNotify.php > /dev/null 2>&1
在这里是www用户已root运行的任务,用jobs -l
www@iZ232eoxo41Z:~ $ jobs -l www@iZ232eoxo41Z:~ $
www@iZ232eoxo41Z:~ $ ps aux | grep nohup root 18448 0.0 0.2 65160 2088 ? S 14:44 0:00 sudo nohup php ./nohupRedisNotify.php root 18449 0.0 1.5 283368 15956 ? S 14:44 0:00 php ./nohupRedisNotify.php www 18605 0.0 0.0 11740 928 pts/1 S+ 14:50 0:00 grep --color=auto nohup
一、今天在Linux服务器执行一个php脚本nohup.php 挂起一个Redis订阅事件的时候,发现每次都不会调用API接口传递参数。可是直接执行的话(php nohup.php)的时候是 能够调用接口的,通过检查发现是权限的问题:
分析:当前登陆用户为www用户:可是启动脚本的时候确实这样的(Root身份执行):(能够在命令行直接执行,打印过时的事件key,而且输出能够做为调试哦!)
sudo nohup php nohupRedisNotify.php > /dev/null 2>&1 &
修改后的:
nohup php nohupRedisNotify.php > /dev/null 2>&1 &
这样的话就直接能够回调本身写的API接口啦!