Use PHP + Redis para implementar tareas retrasadas y cancelar pedidos automáticamente

Solución de tarea de temporización simple: use notificaciones de espacio de teclas de redis (notifique eventos después de fallas de clave). Tenga en cuenta que esta función se lanzó después de la versión 2.8 de redis, por lo que las reids en su servidor deben ser al menos de la versión 2.8 o superior;
(A) Escenario empresarial:

  1. Cuando se activa una empresa, es necesario iniciar una tarea cronometrada y luego realizar una tarea dentro de un tiempo específico (como cancelación automática de pedidos, finalización automática de pedidos, etc.)
  2. Las notificaciones del espacio de claves de redis enviarán un evento después de que la clave falle, y el cliente que escucha este evento puede recibir la notificación.

(B) Preparación del servicio:

Modifique el archivo de configuración de redis (redis.conf) [el archivo de configuración del sistema de ventanas es: redis.windows.conf] Redis
no habilita las notificaciones de espacio de claves de forma predeterminada, porque consumirá la CPU después de la apertura.
Nota: E: evento de evento, evento es __keyevent @ __ es el prefijo para la publicación;
x: evento de expiración, que se generará cuando una clave expire y se elimine; la

configuración original es:

notificar eventos de espacio de claves ""

Cambie la configuración de la siguiente manera:

notificar-espacio-de-teclas-eventos "Ex"

Después de guardar la configuración, reinicie el servicio Redis para que la configuración surta efecto.

[root @ chokingwin etc] # service redis-server restart
/usr/local/redis/etc/redis.conf Deteniendo redis-server: [OK]
Iniciando redis-server: ventana [OK] El sistema reinicia redis
, primero cambie al Redis file Directory, luego cierre el servicio redis (redis-server
--service-stop) y luego abra (redis-server --service-start)

C) Código de archivo:

  • phpredis realiza la suscripción a la notificación de Keyspace, que puede realizar la cancelación automática de pedidos y la finalización automática de pedidos. El siguiente es un ejemplo de prueba

    Cree 4 archivos, luego modifique la base de datos y redistribuya los parámetros de configuración
    db.class.php


<?php
class mysql
{
    private $mysqli;
    private $result;
    /**
     * 数据库连接
     * @param $config 配置数组
     */

    public function connect()
    {
        $config=array(
            'host'=>'172.17.0.1',
            'username'=>'root',
            'password'=>'123456',
            'database'=>'learn_knowleadge',
        );
        $host = $config['host']; //主机地址
        $username = $config['username'];//用户名
        $password = $config['password'];//密码
        $database = $config['database'];//数据库
        $this->mysqli = new mysqli($host, $username, $password, $database);
    }
    /**
     * 数据查询
     * @param $table 数据表
     * @param null $field 字段
     * @param null $where 条件
     * @return mixed 查询结果数目
     */
    public function select($table, $field = null, $where = null)
    {
        $sql = "SELECT * FROM `{$table}`";
        if (!empty($field)) {
            $field = '`' . implode('`,`', $field) . '`';
            $sql = str_replace('*', $field, $sql);
        }
        if (!empty($where)) {
            $sql = $sql . ' WHERE ' . $where;
        }

        $this->result = $this->mysqli->query($sql);

        return $this->result;
    }
    /**
     * @return mixed 获取全部结果
     */
    public function fetchAll()
    {
        return $this->result-> fetch_array();
//        return $this->result->fetch_all(MYSQLI_ASSOC);
    }
    /**
     * 插入数据
     * @param $table 数据表
     * @param $data 数据数组
     * @return mixed 插入ID
     */
    public function insert($table, $data)
    {
        foreach ($data as $key => $value) {
            $data[$key] = $this->mysqli->real_escape_string($value);
        }
        $keys = '`' . implode('`,`', array_keys($data)) . '`';

        $values = '\''.implode("','", array_values($data)) . '\'';

        $sql = "INSERT INTO `{$table}`( {$keys} )VALUES( {$values} )";
        $this->mysqli->query($sql);
        return $this->mysqli->insert_id;
    }
    /**
     * 更新数据
     * @param $table 数据表
     * @param $data 数据数组
     * @param $where 过滤条件
     * @return mixed 受影响记录
     */
    public function update($table, $data, $where)
    {
        foreach ($data as $key => $value) {
            $data[$key] = $this->mysqli->real_escape_string($value);
        }
        $sets = array();
        foreach ($data as $key => $value) {
            $kstr = '`' . $key . '`';
            $vstr = '\'' . $value . '\'';
            array_push($sets, $kstr . '=' . $vstr);
        }
        $kav = implode(',', $sets);
        $sql = "UPDATE `{$table}` SET {$kav} WHERE {$where}";
        $this->mysqli->query($sql);
        return $this->mysqli->affected_rows;
    }
    /**
     * 删除数据
     * @param $table 数据表
     * @param $where 过滤条件
     * @return mixed 受影响记录
     */
    public function delete($table, $where)
    {
        $sql = "DELETE FROM `{$table}` WHERE {$where}";
        $this->mysqli->query($sql);
        return $this->mysqli->affected_rows;
    }
}

Redis.2.class.php


<?php
class mysql
{
    private $mysqli;
    private $result;
    /**
     * 数据库连接
     * @param $config 配置数组
     */

    public function connect()
    {
        $config=array(
            'host'=>'172.17.0.1',
            'username'=>'root',
            'password'=>'123456',
            'database'=>'learn_knowleadge',
        );
        $host = $config['host']; //主机地址
        $username = $config['username'];//用户名
        $password = $config['password'];//密码
        $database = $config['database'];//数据库
        $this->mysqli = new mysqli($host, $username, $password, $database);
    }
    /**
     * 数据查询
     * @param $table 数据表
     * @param null $field 字段
     * @param null $where 条件
     * @return mixed 查询结果数目
     */
    public function select($table, $field = null, $where = null)
    {
        $sql = "SELECT * FROM `{$table}`";
        if (!empty($field)) {
            $field = '`' . implode('`,`', $field) . '`';
            $sql = str_replace('*', $field, $sql);
        }
        if (!empty($where)) {
            $sql = $sql . ' WHERE ' . $where;
        }

        $this->result = $this->mysqli->query($sql);

        return $this->result;
    }
    /**
     * @return mixed 获取全部结果
     */
    public function fetchAll()
    {
        return $this->result-> fetch_array();
//        return $this->result->fetch_all(MYSQLI_ASSOC);
    }
    /**
     * 插入数据
     * @param $table 数据表
     * @param $data 数据数组
     * @return mixed 插入ID
     */
    public function insert($table, $data)
    {
        foreach ($data as $key => $value) {
            $data[$key] = $this->mysqli->real_escape_string($value);
        }
        $keys = '`' . implode('`,`', array_keys($data)) . '`';

        $values = '\''.implode("','", array_values($data)) . '\'';

        $sql = "INSERT INTO `{$table}`( {$keys} )VALUES( {$values} )";
        $this->mysqli->query($sql);
        return $this->mysqli->insert_id;
    }
    /**
     * 更新数据
     * @param $table 数据表
     * @param $data 数据数组
     * @param $where 过滤条件
     * @return mixed 受影响记录
     */
    public function update($table, $data, $where)
    {
        foreach ($data as $key => $value) {
            $data[$key] = $this->mysqli->real_escape_string($value);
        }
        $sets = array();
        foreach ($data as $key => $value) {
            $kstr = '`' . $key . '`';
            $vstr = '\'' . $value . '\'';
            array_push($sets, $kstr . '=' . $vstr);
        }
        $kav = implode(',', $sets);
        $sql = "UPDATE `{$table}` SET {$kav} WHERE {$where}";
        $this->mysqli->query($sql);
        return $this->mysqli->affected_rows;
    }
    /**
     * 删除数据
     * @param $table 数据表
     * @param $where 过滤条件
     * @return mixed 受影响记录
     */
    public function delete($table, $where)
    {
        $sql = "DELETE FROM `{$table}` WHERE {$where}";
        $this->mysqli->query($sql);
        return $this->mysqli->affected_rows;
    }
}

index.php

<?php
require_once 'Redis2.class.php';

$redis = new \Redis2('172.17.0.1','6379','','15');
$order_sn = 'SN'.time().'T'.rand(10000000,99999999);

$use_mysql = 1; //是否使用数据库,1使用,2不使用
if($use_mysql == 1){
    require_once 'db.class.php';
    $mysql = new mysql();
    $mysql->connect();
    $data = array('ordersn'=>$order_sn,'status'=>0,'createtime'=>date('Y-m-d H:i:s',time()));
    $mysql->insert('order',$data);
}
$list = array($order_sn,$use_mysql);
$key = implode(':',$list);
$redis->setex($key,10,'redis延迟任务'); //3秒后回调
$test_del = false; //测试删除缓存后是否会有过期回调。结果:没有回调
if($test_del == true){
    //sleep(1);
    $redis->delete($order_sn);
}
echo $order_sn;
/*
 * 测试其他key会不会有回调,结果:有回调
 * $k = 'test';
 * $redis2->set($k,'100');
 * $redis2->expire($k,10);
 *
*/

psubscribe.php

<?php
ini_set('default_socket_timeout', -1); //不超时
require_once 'Redis2.class.php';
$redis_db = '15';
$redis = new Redis2('172.17.0.1','6379','',$redis_db);
// 解决Redis客户端订阅时候超时情况
$redis->setOption();
//当key过期的时候就看到通知,订阅的key __keyevent@<db>__:expired 这个格式是固定的,db代表的是数据库的编号,
//由于订阅开启之后这个库的所有key过期时间都会被推送过来,所以最好单独使用一个数据库来进行隔离
$redis->psubscribe(array('__keyevent@'.$redis_db.'__:expired'), 'keyCallback');
// 回调函数,这里写处理逻辑
function keyCallback($redis, $pattern, $channel, $msg)
{
    echo PHP_EOL;
    echo "Pattern: $pattern\n";
    echo "Channel: $channel\n";
    echo "Payload: $msg\n\n";
    $list = explode(':',$msg);
    $order_sn = isset($list[0])?$list[0]:'0';
    $use_mysql = isset($list[1])?$list[1]:'0';
    if($use_mysql == 1){
        require_once 'db.class.php';
        $mysql = new mysql();
        $mysql->connect();
        $where = "ordersn = '".$order_sn."'";
        $mysql->select('order','',$where);
        $finds=$mysql->fetchAll();
        print_r($finds);
        if(isset($finds['status']) && $finds['status']==0){
            $data = array('status' => 3);
            $where = " id = ".$finds['id'];
            $mysql->update('order',$data,$where);
        }
    }
}

Método de prueba del sistema de ventanas: primero ejecute psubscribe.php en la interfaz de comando cmd y luego abra index.php en la página web.

Hacer que el fondo de escucha siempre se ejecute (suscripción)

Hay un problema para realizar este paso. Use la extensión phpredis para monitorear exitosamente la clave vencida en el código y realice el procesamiento de devolución de llamada en psCallback (). Se han cumplido los dos requisitos mencionados al principio. Pero hay un problema aquí: una vez que redis termina de realizar la operación de suscripción, el terminal entra en un estado de bloqueo y debe mantenerse allí. Y este script de suscripción debe ejecutarse manualmente en la línea de comando, lo que no satisface las necesidades reales.

De hecho, nuestro requisito para la devolución de llamada de monitoreo vencida es que se ejecute en segundo plano como un demonio y active la función de devolución de llamada cuando haya un mensaje de evento vencido. Haga que el fondo de escucha se ejecute siempre, con la esperanza de estar en segundo plano como un demonio,

Así lo logré.

Hay un comando nohup en Linux. La función es ejecutar comandos sin colgar. Al mismo tiempo, nohup coloca toda la salida del programa de secuencia de comandos en el archivo nohup.out del directorio actual. Si el archivo no se puede escribir, colóquelo en el archivo <directorio de inicio del usuario> /nohup.out. Entonces, con este comando, no importa si nuestra ventana de terminal está cerrada o no, podemos mantener nuestro script php en ejecución.

Nota : Al principio, declaramos la ruta del compilador php:

#! / usr / bin / env php

Esto es necesario para ejecutar scripts php.

Entonces, nohup no se cuelga para ejecutar psubscribe.php, preste atención al & al final

[root @ chokingwin HiGirl] # nohup ./psubscribe.php & [1] 4456 nohup:
ignorando la entrada y agregando salida a `nohup.out '

Explicación: De hecho, el script se ha ejecutado en el proceso 4456.

Compruebe nohup.out cat Compruebe nohuo.out para ver si hay una salida desactualizada:

[root @ chokingwin HiGirl] # cat nohup.out
Patrón: keyevent @ 0 : expired Canal: keyevent @ 0 : expired
Payload: nombre

Ejecute index.php, después de 3 segundos, el efecto es el anterior y es exitoso

Se encontró un problema: use el modo de línea de comando para abrir el script de monitoreo, después de un período de tiempo se informa un error: Error al enviar el paquete QUERY. PID = xxx

Solución: debido a que la cola de mensajes en espera es una conexión larga y hay una conexión a la base de datos antes de esperar la devolución de llamada, el wait_timeout = 28800 de la base de datos, por lo que siempre que el siguiente mensaje esté a más de 8 horas del mensaje anterior, este error Establezca wait_timeout en 10, y capturó la excepción, descubrió que el error real es que el servidor MySQL ha desaparecido,
por lo que siempre que se procese toda la lógica comercial, la conexión de la base de datos se cierra activamente, es decir, la conexión de la base de datos es activamente cerrado para resolver el problema

La solución yii es la siguiente:

Yii :: $ aplicación-> db-> close ();

Ver método de proceso:

ps -aux | grep psubscribe.php

a: Mostrar todos los programas
u : Mostrar todos los programas en un formato orientado al usuario
x: Mostrar todos los programas, no distinguidos por terminal
Ver trabajos ID de proceso: comando [trabajos -l]

www @ iZ232eoxo41Z: ~ / tinywan $ jobs -l [1] - 1365 Detenido (salida tty)
sudo nohup psubscribe.php> / dev / null 2> & 1 [2] + 1370 Detenido (
salida tty ) sudo nohup psubscribe.php> / dev / null 2> & 1

Método para terminar el proceso que se ejecuta en segundo plano:

kill -9 número de proceso

Borrar método de archivo nohup.out:

cat / dev / null> nohup.out Cuando
usamos nohup, generalmente lo usamos junto con &, pero en el uso real, muchas personas cuelgan el programa en segundo plano y simplemente lo ignoran. De hecho, puede ser anormal en la cuenta corriente. Al salir o finalizar, el comando termina por sí mismo.

Entonces, después de usar el comando nohup para ejecutar el comando en segundo plano, debemos hacer lo siguiente:

  1. Presione Entrar para salir del indicador de nohup.
  2. Luego ejecute exit para salir de la cuenta actual normalmente.
  3. Luego vaya al terminal de enlace. Haga que el programa se ejecute normalmente en segundo plano.

Deberíamos usar exit cada vez para salir, en lugar de cerrar la terminal cada vez que nohup se ejecute con éxito. Esto asegurará que el comando se haya estado ejecutando en segundo plano.

Supongo que te gusta

Origin blog.csdn.net/kevlin_V/article/details/104540422
Recomendado
Clasificación