本文所說的定時任務或者說計劃任務並非不少人想象中的那樣,好比說天天凌晨三點自動運行起來跑一個腳本。這種都已經爛大街了,隨便一個 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接口啦!