Implement Redis Queue and Worker with PHP on Ubuntu 20.04

Updated on February 20, 2021
Implement Redis Queue and Worker with PHP on Ubuntu 20.04 header image

Introduction

Redis queue is a library that you can use to create and process a linear collection of jobs on a first-in-first-out basis. You should always use queues in your web applications to avoid keeping end-users waiting for a response if your business logic takes a long time to complete. For instance, in a busy online store, you can perform complex processes like payment and address verification using queues and background workers. In other words, if you have a use-case where you want processes to occur in an orderly manner but your web application can't keep up with the end user's patience, consider using a Redis queue, such as a file conversion portal.

In this guide, you'll use the Redis RPUSH and LPOP commands with PHP and MySQL to process customers' registrations in a MySQL database on Ubuntu 20.04 server.

Prerequisites

To complete the tutorial, you'll need the following:

Install php-redis Extension

Connect to your server, update the package information index and install php-redis helper library that allows you to use the Redis server's functionalities inside your PHP code.

$ sudo apt update
$ sudo apt install -y php-redis

Restart the Apache webserver to reload the changes.

$ sudo systemctl restart apache2

Create a test_db Database

In this tutorial, you will queue the customers' registration information in the Redis server, but later, you'll persistently save the data in the MySQL database.

Run the command below to log in to the MySQL server as a root user.

$ sudo mysql -u root -p

When prompted, key in the root password of your MySQL server and press Enter to proceed. Then, create a test_db database.

mysql> CREATE DATABASE test_db;

Set up a test_db_user user and use a strong value for the EXAMPLE_PASSWORD. You will use these details to connect to MySQL from your PHP script.

mysql> CREATE USER 'test_db_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'EXAMPLE_PASSWORD';
mysql> GRANT ALL PRIVILEGES ON test_db.* TO 'test_db_user'@'localhost';
mysql> FLUSH PRIVILEGES;

Change the CREATE USER command to the below syntax if you're using the MariaDB server.

MariaDB> GRANT ALL PRIVILEGES on test_db.* TO 'test_db_user'@'localhost' identified by 'EXAMPLE_PASSWORD';

Then, switch to the test_db database.

mysql> USE test_db;

Create a customers table by running the command below.

mysql> CREATE TABLE customers (
       customer_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
       first_name VARCHAR(50),
       last_name VARCHAR(50),
       email_address VARCHAR(255)
       ) ENGINE = InnoDB;

Don't enter any data into the customers table, you'll later populate the table using a job that fetches data from the Redis server. Exit from the mysql> command-line interface.

mysql> QUIT;

Set Up a Redis Queue PHP File

Open a new /var/www/html/redis_queue.php using nano in the root directory of your web server. This file will receive POST parameters with customer details for registration purposes.

$ sudo nano /var/www/html/redis_queue.php

Add the information below to the file.

<?php
    try {
            $redis = new Redis();
            $redis->connect('127.0.0.1', 6379);

            $data = [];
            $data = [
                    'first_name'    => $_POST['first_name'],
                    'last_name'     => $_POST['last_name'],
                    'email_address' => $_POST['email_address']
                    ];

            $redis->rpush("customers_register", json_encode($data));

            echo "Customer details accepted successfully.\n";

        } catch (Exception $e) {
            echo $e->getMessage();
        }

Save and close the file.

In the file above, you're connecting to the locally hosted Redis server at port 6379. Then, you're creating a $data array of the customer's data as received from the global $_POST parameter. Then, you're pushing the data as a JSON encoded string to the Redis server using the $redis->rpush command.

Once the data is saved to the Redis server, you're echoing out a success message. In the next step, you'll send sample data to the file.

Send Customer's Data to the Redis Queue

In a production environment, customers may subscribe to your application using HTML forms. For the basis of this tutorial, you'll just use the Linux curl command to send the data to the http://localhost/queue_register.php that you've created above.

Send three customers' records by running the curl commands below one by one.

$ curl --data "first_name=JOHN&last_name=DOE&email_address=john_doe@example.com" http://localhost/redis_queue.php
$ curl --data "first_name=MARY&last_name=SMITH&email_address=mary_smith@example.com" http://localhost/redis_queue.php
$ curl --data "first_name=ROE&last_name=STEVE&email_address=roe_steve@example.com" http://localhost/redis_queue.php

Ensure you get the following output after issuing each command.

...

Customer details accepted successfully.

The output above confirms that the Redis server is queueing data successfully. You'll now move on to creating a worker for processing the queue.

Save the Redis Data Permanently to MySQL

In this step, you'll use the Redis LPOP command to dequeue the customers' data from the Redis server and permanently save the data to the MySQL database.

Open a new /var/www/html/redis_worker.php file using nano.

$ sudo nano /var/www/html/redis_worker.php

Then, add the information below into the file.

<?php
    try {
            $redis = new Redis();
            $redis->connect('127.0.0.1', 6379);

            $data = $redis->lpop('customers_register');
            $data  = json_decode($data, true);

            if (!empty($data)) {
               $db_name     = 'test_db';
               $db_user     = 'test_db_user';
               $db_password = 'EXAMPLE_PASSWORD';
               $db_host     = 'localhost';

               $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
               $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
               $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

               $sql = 'insert into customers
                      (
                      first_name,
                      last_name,
                      email_address
                      )
                      values
                      (
                      :first_name,
                      :last_name,
                      :email_address
                      )
                      ';

               $stmt = $pdo->prepare($sql);
               $stmt->execute($data);

               echo $data[first_name] . " " . $data[last_name] . "'s details saved to database.";

            }

        } catch (Exception $e) {
            echo $e->getMessage();
        }

Save and close the file.

In the above file, you're connecting to the Redis server and retrieving customers' data on a first-in-first-out basis using $redis->lpop statement. Next, you've JSON decoded the data and constructed an SQL command to be executed by the PHP Data Object($pdo). Finally, you're saving the data permanently to the MySQL server and echoing out a success message.

Please note, you're using bound parameters to prevent SQL injection attacks. This guide is for demonstration purposes and does not include any form of data validation. In a production environment, you would sanitize and filter the data even further to have a solid security layer.

Testing the Redis Server Worker

Execute the http://localhost/redis_worker.php URL to test if the redis_worker.php file is working as expected. Run the command below three times to process all the entries.

$ curl http://localhost/redis_worker.php

As you can see from the output below, the Redis LPOP command is successfully registering the customers in the database in the order their data arrived in the Redis server.

JOHN DOE's details saved to database.
MARY SMITH's details saved to database.
ROE STEVE's details saved to database.

Confirm the customers' details from the database to make sure they are in place. To do this, log back to the MySQL server as root.

$ sudo mysql -u root -p

Enter your password and press Enter to continue. Then, switch to the test_db database.

mysql> USE test_db;

Run a SELECT statement against the customers table.

mysql> SELECT
       customer_id,
       first_name,
       last_name,
       email_address
       FROM customers;

You should see the output below to confirm that the Redis queue's and worker's logic are working as expected.

+-------------+------------+-----------+------------------------+
| customer_id | first_name | last_name | email_address          |
+-------------+------------+-----------+------------------------+
|           1 | JOHN       | DOE       | john_doe@example.com   |
|           2 | MARY       | SMITH     | mary_smith@example.com |
|           3 | ROE        | STEVE     | roe_steve@example.com  |
+-------------+------------+-----------+------------------------+
3 rows in set (0.00 sec)

Exit from the MySQL command-line interface

mysql> QUIT;

Run the Redis Worker Automatically

In the previous step, you've run the http://localhost/redis_worker.php script manually using the Linux curl command. To automatically run the worker, you need to create a cron job that executes the file without your action.

Open the crontab file.

$ sudo nano /etc/crontab

Add the content below to the bottom of the file to run the http://localhost/redis_worker.php worker every minute.

...
* * * * * root /usr/bin/wget -O - http://localhost/redis_worker.php

Save and close the file. Now, send another customer's data to the http://localhost/redis_queue.php endpoint.

$ curl --data "first_name=BABY&last_name=SMALL&email_address=baby_small@example.com" http://localhost/redis_queue.php

Make sure the customer's details are queued.

Customer details accepted successfully.

Log back to the MySQL server after one minute.

$ sudo mysql -u root -p

Key in the root password of your MySQL server and press Enter. Then, use the test_db database.

mysql> USE test_db;

Execute the SELECT statement below one more time to see if the cron job is executing the worker.

mysql> SELECT
       customer_id,
       first_name,
       last_name,
       email_address
       FROM customers;

You should get the out below confirming that the cron job is working as expected.

+-------------+------------+-----------+------------------------+
| customer_id | first_name | last_name | email_address          |
+-------------+------------+-----------+------------------------+
|           1 | JOHN       | DOE       | john_doe@example.com   |
|           2 | MARY       | SMITH     | mary_smith@example.com |
|           3 | ROE        | STEVE     | roe_steve@example.com  |
|           4 | BABY       | SMALL     | baby_small@example.com |
+-------------+------------+-----------+------------------------+
4 rows in set (0.00 sec)

A one-minute time-period may be too long for some mission-critical applications, and in such cases, you can create a PHP loop file that keeps polling the worker file for new queue entries.

Conclusion

You've used the Redis server to queue and process jobs with PHP on Ubuntu 20.04 server in this guide. You may extend the code in this guide to suit your needs.