背景概要
- 背景:為了防止用戶長時間不確認收貨,需要對訂單做24小時后自動確認收貨處理
- 方案:利用Redis的鍵空間通知和發(fā)布訂閱機制,結合nohup掛載命令后臺守護進程監(jiān)聽,可實現(xiàn)Redis的key過期自動提醒
- 結果:最終實現(xiàn)訂單確認收貨超時自動化
其他方案
一開始想到的是cron定時任務,雖然可以勉強滿足但不是最佳方案,訂單數(shù)少的時候很浪費資源
實現(xiàn)
修改redis配置
因為開啟鍵空間通知功能需要消耗一些 CPU , 所以在默認配置下, 該功能處于關閉狀態(tài)。

127.0.0.1:6379> config get notify-keyspace-events
1) "notify-keyspace-events"
2) ""
127.0.0.1:6379> config set notify-keyspace-events Ex
OK
127.0.0.1:6379> config get notify-keyspace-events
1) "notify-keyspace-events"
2) "xE"
其實不建議用上面的辦法,因為config是危險命令,生產(chǎn)環(huán)境禁用,任何客戶端都能隨便獲取配置修改配置,只要客戶端連接成功就能隨意篡改,只能在測試服使用
Centos修改配置方法還不太一樣
修改文件/etc/redis.conf
redis-cli shutdown
redis-server /etc/redis.conf &
vim /etc/redis.conf
此時重新進入 redis-cli 查看 config get notify-keyspace-events 就看到配置生效了
重啟redis
/etc/init.d/redis-server restart
redis修改了配置文件,但是一直不生效怎么辦,看這篇https://www.cnblogs.com/foundwant/p/5552807.html
鍵空間通知演示
127.0.0.1:6379> setex name 10 shizhenfeng
OK
再打開一個終端,10秒之后~
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"
4) "name"
結合Laravel artisan實現(xiàn)
創(chuàng)建命令
php artisan make:command OrderExpireListen
代碼如下
<?php
namespace App\Console\Commands;
use App\Models\Order;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
class OrderExpireListen extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'order:expire';
/**
* The console command description.
*
* @var string
*/
protected $description = '24小時自動確認收貨';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$pattern = '__keyevent@0__:expired';
Redis::subscribe($pattern, function ($channel) { // 訂閱鍵過期事件
// 鍵格式:order_confirm:34
$keyType = str_before($channel, ':');
switch ($keyType) {
case 'order_confirm':
$orderId = str_after($channel, ':');
$order = Order::find($orderId);
if ($order) {
$order->status = 5;
$order->save();
\Log::info("訂單 {$order->id} 自動確認收貨");
}
break;
case 'order_other':
// 其他事件,如訂單15分鐘內(nèi)未支付自動取消訂單
break;
default:
break;
}
});
}
}
控制器
// redis鍵空間通知
$key = 'order_confirm:' . $order->id;
$seconds = 24 * 60 * 60;
// 值無所謂,關鍵是key
Redis::setex($key, $seconds, 1);
報錯記錄
Error while reading line from the server. [tcp://127.0.0.1:6379]
解決辦法:
config/database.php 添加一行
'redis' => [
'client' => 'predis',
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
'read_write_timeout' => 0, // <----這一條是新加的
],
],
如何使監(jiān)聽后臺始終運行
redis 在執(zhí)行完訂閱操作后,終端進入阻塞狀態(tài),需要一直掛在那。且此訂閱腳本需要人為在命令行執(zhí)行,不符合實際需求。
實際上,我們對過期監(jiān)聽回調的需求,是希望它像守護進程一樣,在后臺運行,當有過期事件的消息時,觸發(fā)回調函數(shù)。 使監(jiān)聽后臺始終運行 希望像守護進程一樣在后臺一樣,
Linux中有一個nohup命令。功能就是不掛斷地運行命令。 同時nohup把腳本程序的所有輸出,都放到當前目錄的nohup.out文件中,如果文件不可寫,則放到<用戶主目錄>/nohup.out 文件中。那么有了這個命令以后,不管我們終端窗口是否關閉,都能夠讓我們的php腳本一直運行。
local環(huán)境
nohup php /var/www/wine/artisan order:expire >> /var/www/wine/storage/logs/nohup.log 2>&1 &
dev環(huán)境
nohup /usr/bin/php /var/www/html/wine/artisan order:expire >> /var/www/html/wine/storage/logs/nohup.log 2>&1 &
如何證明命令掛上去了
方法一
ps -ef |grep php
控制臺輸出
root 1216 1 0 01:10 ? 00:00:02 php-fpm: master process (/etc/php/7.1/fpm/php-fpm.conf)
vagrant 1473 1216 0 01:10 ? 00:00:01 php-fpm: pool www
vagrant 1484 1216 0 01:10 ? 00:00:00 php-fpm: pool www
vagrant 1489 1 0 01:10 ? 00:00:01 /usr/bin/hhvm --config /etc/hhvm/php.ini --config /etc/hhvm/server.ini --user vagrant --mode daemon -vPidFile=/var/run/hhvm/pid
vagrant 7909 2842 0 08:24 pts/0 00:00:00 php /var/www/wine/artisan order:expire <--------看,這里就有了吧
vagrant 7974 2842 0 08:29 pts/0 00:00:00 grep --color=auto php
方法二(只對當前會話有效,關掉窗口就看不到了)
jobs -l
控制臺輸出
[1]+ 7909 Running nohup php /var/www/wine/artisan order:expire >> /var/www/wine/storage/logs/nohup.log 2>&1 &
拓展
還能用到哪些需求,比如未支付訂單定時關閉