Xử lý vấn đề Timeout khi Push notify cho nhiều devices

Chào các bạn, để tiếp nối bài viết Server Cloud Message lần trước của mình, hôm nay mình sẽ nói thêm một vài thứ, cái mà bạn có thể sẽ gặp phải khi push notification.

Bài toán giả tưởng:

Trong đang viết một app tìm kiếm shipper, khi các saler của bạn cần tìm một shipper, họ sẽ mở app và đăng ký tìm một shipper, ngay lập tức hệ thống của bạn sẽ phải gửi notify cho tất cả các thánh shipper đang sử dụng app của bạn là có người đang tìm người ship và trả về thông báo đăng ký tìm ship thành công. Vấn đề ở đây là nếu bạn gửi notify cho cả ngàn người ngay lập tức khi gọi tới API tạo mới công việc, thì app của bạn sẽ rất chậm hoặc sảy là vấn đề timeout. Giải pháp ở đây có thể là tạo ra một Thread, cronjob còn hôm nay mình sẽ sử dụng Queue trong Laravel để giải quyết vấn đề này. Dài dòng quá, nếu các bạn vẫn k hiểu mình nói gì thì sorry nhé 😄

Giải pháp

Gửi notify cho nhiều device cùng một thời điểm thì Firebase cũng đã hỗ trợ thêm cho mình 2 có chế là Push Topic và Push theo Group Device các bạn có thể xem thêm ở Firebase Document
Ở đây mình chỉ hướng dẫn qua về push Topic thui, còn group devices thì bạn tự tìm hiểu nhé, nó cũng rất simple thui. Với push topic thì bạn chỉ cần sửa phần ví dụ trọng push theo device của mình ở bài trước thành topic và clients nó sẽ lắng nghe topic đó.

Push Topic

PHP
function sendCloudMessaseToAndroid($topic = '' $message = '', $push_data = array()) {        
    $url = 'https://fcm.googleapis.com/fcm/send ';
    $serverKey = "*** Key mà ở trên mình đã bảo bạn copy ra ý ***";
    $msg = array(
        'message' => $message,
        'data' => $push_data
    );            
    $fields = array();
        $fields['data'] = $msg;
        $fields['to'] = "/topics/$topic";
        $headers = array(
            'Content-Type:application/json',
            'Authorization:key='.$server_key
        );
    $headers = array(
        'Content-Type:application/json',
        'Authorization:key=' . $serverKey
    );   
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
    $result = curl_exec($ch);
    if ($result === FALSE) {
        die('FCM Send Error: '  .  curl_error($ch));
    }
    curl_close($ch);
    return $result;
}   

Dùng Queue trong Laravel

Quay lại với nội dung chính là mình sẽ sử dụng Queue trong Laravel. Sử dụng Queue thế nào thì Laravel đã nói quá đủ rồi nhưng có thể nhiều bạn cũng chưa rõ cách triển khai nó thế nào. Nên mình sẽ áp dụng nó để giải quyết bài toán của mình (code). Để bắt đầu thì bạn cần config connections trong file .env cho Queue, ở đây mình chỉ dùng database nên:

Edit from  QUEUE_DRIVER=sync To  QUEUE_DRIVER=database

OK rồi, hiểu đơn giản nó như config database thôi Tiếp đến là tạo bảng lưu trữ các Queue cần thực hiện

php artisan queue:table

Sau đó là tạo bảng failed_jobs để queues có thể lưu những queue thực hiện fail để Queue lưu những jobs fail vào đó

php artisan queue:failed-table

Và rồi chạy:

php artisan migrate

Xong, công việc config trước khi tạo Queue đã hoàn thành Giờ mình sẽ tạo 1 Command để Queue gọi đến mỗi khi cần push notify cho devices

php artisan make:command PushNotifyToDevices

Implement command created :

<?php namespace App\Commands;

use App\Commands\Command;

use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Support\Facades\Log;
use App\Models\Notify\Notify;
use App\Notify_missed_booking;

class PushNotifyToDevices extends Command implements SelfHandling {

	/**
	 * Create a new command instance.
	 *
	 * @return void
	 */
	public function __construct($devices, $message, $pushData, $bookingId)
	{
		$this->devices = $devices;
		$this->message = $message;
		$this->pushData = $pushData;
		$this->bookingId = $bookingId;
	}

	/**
	 * Execute the command.
	 *
	 * @return void
	 */
	public function handle()
	{
		$customers = $this->devices;
		$message = $this->message;
		$pushData = $this->pushData;
        
        $missed['push_data'] = $pushData;
        $missed ['booking_id'] = $this->bookingId;
		foreach($customers as $customer)
		{
			if ($customer->type_device == 2) { /* Push IOS */

                $result = Notify::Push2Ios($customer->device_token, $message, $pushData);
				$result['success'] = 0;

			} else { /* Push Android */

            	$result = Notify::cloudMessaseAndroid($customer->device_token, $message, $pushData);
            	$result = json_decode($result, true);

			}

            $missed['note'] = 'Missed push notify ' . $customer->device_token;
            $missed['laodong_id'] = $customer->id;

            if ($result['success'] != 1) {
                $missed ['status'] = 0;
                Notify_missed_booking::SaveData($missed);
            } else {
                $missed ['status'] = 1;
                Notify_missed_booking::SaveData($missed);
            }

        }
	}

}

Bây giờ trong function tìm ship, thay vì for để gửi cho từng device một, mình sẽ gọi đến Queue để tách riêng luồng push notify ra bên ngoài và trả về thông báo thành công cho người dùng

    public function findShipper()
    {
        $result = array();
        /*
        
        Code save data
        
        */
        /*
        Nếu bạn có quá nhiều devices, bạn hãy slice nó ra thành nhiều mảng, sau đó new nhiều
        đối tượng PushNotifyToDevices để nó push nhanh hơn nhé. tương tự new Thread 
        */
        Queue::later(5, new PushNotifyToDevices($customers, $loaidichvu, $dataPushed, $booking_id));
        return $result;
    }

Với function trên, server sẽ xử lý thông tin và trả về response cho người dùng, sau đó 5s thì Queue sẽ thực hiện push notify cho devices, với cách làm này người dùng sẽ ko mất thời gian đợi push notify trước khi nhận đc response Việc còn lại là bạn vào command chạy lệnh

php artisan queue:listen

Câu lệnh này sẽ chạy background để lắng nghe các queue khi được gọi, nghĩa là nếu bạn quên k chạy lệnh này thì các Queue của bạn sẽ không hoạt động. Vậy thì làm thế nào để lệnh này của tôi luôn luôn chạy, ko lẽ mỗi lần tắt terminal thì em nó ko hoạt động sao? Đừng lo, bạn có thể dùng nohup hoặc nâng cao hơn là screen để xử lý vấn đề này. Screen là gì thì bạn có thể đọc trên mạng hoặc search nhé, do đã lan man quá nhiều thứ trong bài viết nên mình sẽ k đề cập nó trong bài viết nữa, mình sẽ có chi tiết hơn ở bài sau, nếu bạn cần hãy comment nhé 😄. Nếu bạn đã cài screen thì hãy tạo thêm 1 screen (session)

Ctrl+a n

Sau đó cd vào sourcecode của bạn để chạy lệnh php artisan queue:listen Cuối cùng là câu lệnh detached để lệnh này luôn chạy để cả khi bạn tắt terminal nhé (bow)

Ctrl+a d

Lan man quá rồi, Mình xin kết thúc bài viết ở đây, bài viết mình viết dựa trên những trải nghiệm thực tế và những nhận thức chủ quan của mình. Rất mong nhận được sự góp ý để mình có thể hoàn thiện bài viết tốt hơn. See you!!!


All Rights Reserved