RabbitMQ實戰《延遲佇列》

老衲愛飲酒發表於2020-08-12

第一次寫部落格,記錄一下RabbitMQ的學習,如果有不正確的地方,還請及時指出

場景

訂單超時未支付,關閉訂單

使用者下單

public function index()
{
    //建立訂單
    $order = new Order();
    $order->order_sn = date('YmdHis').time();
    $order->user_id = 1;
    $order->product_id = 1;
    $order->save();

    //推送至佇列
    (new OrderService())->push($order);
    //返回相關資訊
    return true;

}

佇列

<?php

namespace App\Service;


use App\Models\Order;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;

class OrderService
{
    const HOST = '192.168.1.199';
    const PORT = '5672';
    const LOGIN = 'guest';
    const PASSWORD = 'guest';
    const VHOST = '/';

    //交換機名稱
    public $exchangeName = 'laravel_exchange_name';

    //普通佇列名稱和路由key
    public $queueName = 'laravel_queue_name';
    public $routeKey = 'laravel_route_key';

    //延遲佇列和路由
    public $delayQueueName = 'laravel_delay_queue_name';
    public $delayRouteKey = 'laravel_delay_route_key';

    //延遲時長
    public $delaySecond = 10;

    public $channel;

    public function __construct()
    {
        $connection = new AMQPStreamConnection(self::HOST,self::PORT,self::LOGIN,self::PASSWORD);
        $this->channel = $connection->channel();

        $this->init();
    }

    public function init()
    {
        // 宣告交換機
        $this->channel->exchange_declare($this->exchangeName, 'direct', false, true, false);

        $this->declareConsumeQueue();
        $this->declareDelayQueue();
    }

    //消費佇列
    private function declareConsumeQueue()
    {
        //宣告消費佇列
        $this->channel->queue_declare($this->queueName, false, true, false, false);
        //繫結交換機及佇列
        $this->channel->queue_bind($this->queueName, $this->exchangeName, $this->routeKey);
    }

    //延遲佇列
    private function declareDelayQueue()
    {
        //設定訊息過期時間
        $tab = new AMQPTable([
            'x-dead-letter-exchange' => $this->exchangeName,    //訊息過期後推送至此交換機
            'x-dead-letter-routing-key' => $this->routeKey,        //訊息過期後推送至此路由地址        //也就是我們消費的正常佇列    與①對應
            'x-message-ttl' => intval($this->delaySecond) * 1000, //10秒
        ]);
        //宣告延遲佇列
        $this->channel->queue_declare($this->delayQueueName,false,true,false,false,false,$tab);
        //繫結交換機及延遲佇列
        $this->channel->queue_bind($this->delayQueueName, $this->exchangeName, $this->delayRouteKey);
    }

    //入佇列
    public function push($order)
    {
        $message = json_encode([
            'id' => $order->id
        ]);

        //建立訊息
        $msg = new AMQPMessage($message, [
            'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
        ]);
        //推送至佇列                   //訊息   //交換機名稱        //路由  推送至延遲佇列中
        $this->channel->basic_publish($msg, $this->exchangeName, $this->delayRouteKey);
    }

    //出佇列
    public function consume()
    {
        //消費  普通消費佇列            //①
        $this->channel->basic_consume($this->queueName, '', false, false, false, false,
            [$this, 'process_message']);

        while (count($this->channel->callbacks)) {
            $this->channel->wait();
        }
    }

    //開始消費
    public function process_message($message)
    {

        $obj = json_decode($message->body);
        try {

            $order = Order::find($obj->id);
            if (strtotime($order->created_at) + $this->delaySecond > time()){
                throw new \Exception('取消訂單時間未到', 404);
            }

            //更改資料庫狀態
            $order->status = 10;
            $order->colsed_at = date('Y-m-d H:i:s');
            $res = $order->save();

            if (!$res){
                throw new \Exception('取消訂單失敗', 404);
            }

        } catch (\Exception $e) {
            //記錄日誌
        }
        //確認訊息處理完成
        $message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
    }

}

使用者下單

當使用者下單以後,延遲佇列就會出現一條待消費的記錄,這裡佇列名稱和我們程式碼中生成的名稱一致,laravel_delay_queue_name

當訊息過期以後,此訊息就會被推送至我們設定好的佇列中,也就是laravel_queue_name,從而會被消費掉,達到超時未支付,取消訂單的效果

定義排程

<?php

namespace App\Console;

use App\Console\Commands\OrderNopay;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
//省略其他程式碼
    protected $commands = [
        OrderNopay::class,
    ];
}
<?php

namespace App\Console\Commands;

use App\Service\OrderService;
use Illuminate\Console\Command;

class OrderNopay extends Command
{
    protected $signature = 'order:nopay';

    protected $description = '訂單超時未支付';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle(OrderService $order)
    {
        $order->consume();
    }
}

執行

可以使用 php artisan order:nopay命令,或者通過supervisor來跑

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章