在 Ubuntu 20.04 上使用带有 PHP 的 Redis Pub/Sub

介绍

Redis Pub/Sub 是一种计算机架构,允许 出版商 发送信息给 订户 通过 频道. 您可以使用此技术构建依赖于 一世过程 C通信(IPC)。 例如,网络聊天、金融系统等。 在这些系统中,速度是游戏的名称,当最终用户连接到通道时,他们希望以尽可能短的延迟接收消息。 在使用传统 SQL 数据库设计高响应应用程序时,您可能会面临可伸缩性问题。 但是,Redis 将数据存储在您服务器的 RAM 中,并且每秒可以执行许多读/写周期。

使用 Redis 实现 Pub/Sub 范式的另一个巨大优势是能够分离发布者和订阅者(​​解耦)。 您只需将数据发送到通道,而无需定义接收者。 同样,订阅者可以对任何频道表现出兴趣并等待消息,而无需了解发布者。

在这种解耦系统中,您的前端和后端工程师可以独立设计和测试他们的构建,减少完成项目所需的总时间,因为一切都是以并行方式完成的。 这提高了开发人员的自由度并简化了招聘流程,因为松散耦合的组件降低了最终应用程序的复杂性。

在本指南中,您将使用 PHP 和 Redis 服务器创建一个分离的水费计费应用程序。 在此系统中,您将使用前端脚本(发布者)接收来自房主的用水量数据并将信息发送到 billings 渠道。 在幕后,您将使用后端脚本(订阅者)来处理来自 billings 通道并将其永久保存到 MySQL 数据库中。

先决条件

要学习本 Redis Pub/Sub 教程,请确保您具备以下条件:

一个 Ubuntu 20.04 服务器。 一个 sudo 用户。 灯组。 一个Redis服务器

1.安装PHP Redis扩展

要通过 PHP 脚本连接到 Redis 服务器,您需要安装 php-redis 延期。 SSH 到您的服务器并更新包信息索引。

$ sudo apt update

接下来,运行以下命令来安装扩展。

$ sudo apt install -y php-redis

重新启动 Apache 网络服务器以加载新更改。

$ sudo systemctl restart apache2

一旦您为从 PHP 连接到 Redis 服务器设置了正确的环境,接下来您将创建一个 MySQL 数据库。

2. 创建示例数据库和用户

Redis 是一个内存数据库,虽然它可以将数据持久化到磁盘,但它并不是为此目的而设计的。 它主要用于处理以性能为中心的数据。 例如,在这个示例水费计费应用程序中,Redis 服务器会及时从数据员那里获取用水量。 然后,您将处理数据并将其永久保存到 MySQL 数据库的磁盘上。

要创建数据库,请以以下身份登录 MySQL 数据库服务器 root.

$ sudo mysql -u root -p

接下来,输入 MySQL 服务器的 root 密码,然后按 ENTER 继续。 然后,运行以下语句来创建一个 billing_db 数据库和连接到它的特权用户。 代替 EXAMPLE_PASSWORD 具有很强的价值。

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

输出。

...
Query OK, 0 rows affected (0.00 sec)

切换到新数据库。

mysql> USE billing_db;

确保您选择了 billing_db 数据库通过确认下面的输出。

Database changed

接下来创建一个 customers 用于存储客户数据的表,包括 customer_id, first_name, 和 last_name.

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

确保您获得以下输出以确认您已创建表。

Query OK, 0 rows affected (0.01 sec)

接下来,使用以下 INSERT 命令将样本数据输入到 customers 桌子。

mysql> INSERT INTO customers (first_name, last_name) values ('JOHN', 'DOE');
       INSERT INTO customers (first_name, last_name) values ('MARY', 'SMITH');
       INSERT INTO customers (first_name, last_name) values ('STEVE', 'JONES');

在表中创建每条记录后,您应该会收到以下确认消息。

...  
Query OK, 1 row affected (0.01 sec)

接下来,设置一个表来处理客户的每月账单。 一旦您收到每个客户的用水量数据,您就会将这些信息存储在一个 billings 桌子。 在该表中, customer_id 指回相同的字段 customers 桌子。 要识别每张帐单,您将使用 ref_id 柱子。 这 billing_period 字段存储发生帐单的月份的最后日期。 这 units_consumed 是房主在此期间实际使用的水的立方米数。 这 cost_per_unit 代表您的样本公司每月以美元 ($) 为单位收取的费用。

执行下面的命令来创建 billings 桌子。

mysql> CREATE TABLE billings (
           ref_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
           customer_id BIGINT,
           billing_period VARCHAR(50),
           units_consumed DOUBLE,
           cost_per_unit  DOUBLE          
       ) ENGINE = InnoDB;

确保你得到下面的输出。

Query OK, 0 rows affected (0.02 sec)

从 MySQL 命令行界面退出。

mysql> QUIT;

输出。

Bye

您现在已经创建了数据库、设置了表并输入了一些示例数据。 在下一步中,您将编写一个 前端 接受来自数据职员的用水数据以用于计费目的的脚本。

3. 在 PHP 上创建前端脚本

在计费系统中,房主的用水量以立方米为单位。 除非在这些家庭中安装智能设备,否则数据文员必须亲自访问并手动记录水表的读数,并将相同的信息传送到公司的数据库。

在现代世界中,这些信息可以通过连接到托管在中央云服务器上的 API 的移动应用程序发送。 在此步骤中,您将创建一个使用 PHP 接受此类数据的前端脚本。 用 nano 创建一个新的 frontend.php 文件在您的 Web 服务器的根目录中。

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

接下来,将以下信息输入到 /var/www/html/frontend.php 文件。

<?php 

    try {   

        $redis = new Redis(); 
        $redis->connect('127.0.0.1', 6379);

        $channel="billings";
        $billing_data = file_get_contents('php://input');          

        $redis->publish($channel, $billing_data); 

        echo "Data successfully sent to the billings channel.n";         

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

完成编辑后保存并关闭文件。 在您继续本指南的其余部分之前,这里是 /var/www/html/frontend.php 解释:

您正在连接到 Redis 服务器 localhost 在港口 6379 使用下面的代码。

$redis = new Redis(); 
$redis->connect('127.0.0.1', 6379);
...

然后,您正在初始化一个新的 $channel 变量并将传入的 JSON 数据检索到脚本中并将其分配给 $billing_data 使用 PHP 变量 file_get_contents('php://input'); 陈述。

...
$channel="billings";
$billing_data = file_get_contents('php://input'); 
...

最后,你 出版 新的 JSON 输入数据到 billings 频道使用 $redis->publish 语句命令并回显成功消息。 此外,您正在捕获使用 PHP 可能遇到的任何错误 catch(...){...} 如下图块。

...

$redis->publish($channel, $billing_data); 

echo "Data successfully sent to the billings channel.n";         

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

您现在已经创建了一个 frontend.php 接收客户账单数据并将其路由到 Redis 通道的脚本。 在下一步中,您将创建一个后端脚本来处理数据。

4. 在 PHP 上创建后端脚本

处理来自 frontend.php 脚本并在收到它时将其保存在 MySQL 数据库中,您将 SUBSCRIBEbillings 通过一个渠道 backend.php 脚本。 用 nano 开一个新的 /var/www/html/backend.php 文件。

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

然后,在文件中输入以下信息。

<?php 

    try {   

        $redis = new Redis(); 
        $redis->connect('127.0.0.1', 6379);
        $redis->setOption(Redis:: OPT_READ_TIMEOUT, -1);       

        $redis->subscribe(['billings'], function($instance, $channelName, $message) { 

            $billing_data = json_decode($message, true); 

            $db_name="billing_db";
            $db_user="billing_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);   

            $sql = "insert into billings(
                        customer_id, 
                        billing_period, 
                        units_consumed,
                        cost_per_unit
                    ) 
                    values(
                        :customer_id, 
                        :billing_period, 
                        :units_consumed,
                        :cost_per_unit
                    )'; 

            $data = [];

            $data = [
                    'customer_id'    => $billing_data['customer_id'],
                    'billing_period' => $billing_data['billing_period'],
                    'units_consumed' => $billing_data['units_consumed'],
                    'cost_per_unit'  => 2.5                                 
                    ];

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

            echo "The Redis data was sent to the MySQL database successfully.n" ;               

        });         

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

完成编辑后保存并关闭文件。 在上面的文件中,您正在连接到 Redis 服务器并订阅 billings 频道使用 $redis->subscribe(['billings'], function(...) {...}) 陈述。 你用过这条线 $redis->setOption(Redis:: OPT_READ_TIMEOUT, -1); 强制脚本永不超时。 一旦您从通道实时接收到数据,您就通过准备好的语句将其发送到 MySQL 服务器。 您的后端脚本现已就绪。 接下来,您将测试 Redis Pub/Sub 逻辑。

5. 测试 Redis 发布/订阅应用程序

在两个不同的终端窗口上连接到您的 Linux 服务器。 在第一个窗口,运行以下命令 backend.php 脚本。

$ php /var/www/html/backend.php

请注意,上面的脚本有一个阻塞功能,可以实时监听和等待来自 Redis 服务器的消息。 因此,您不应在运行脚本后尝试执行任何其他命令。 现在,不要指望脚本有任何输出。 在第二个终端窗口中,运行 curl 下面的命令将数据发送到 frontend.php 脚本。

$ curl -X POST http://localhost/frontend.php -H 'Content-Type: application/json' -d '{"customer_id":1, "billing_period": "2021-08-31", "units_consumed":12.36}'
$ curl -X POST http://localhost/frontend.php -H 'Content-Type: application/json' -d '{"customer_id":2, "billing_period": "2021-08-31", "units_consumed":40.20}'
$ curl -X POST http://localhost/frontend.php -H 'Content-Type: application/json' -d '{"customer_id":3, "billing_period": "2021-08-31", "units_consumed":24.36}'

你正在使用 curl 将数据发送到本指南中的前端脚本以证明该概念。 在生产环境中,数据文员可能会通过移动应用程序、桌面应用程序或 Web 应用程序捕获信息。 每次执行后 curl 命令,您应该会收到以下输出,显示数据已成功发送到 Redis billings 渠道。

...
Data successfully sent to the billings channel.

在第二个窗口中 /var/www/html/backend.php 脚本是 订阅了billings 通道和侦听消息,您应该收到以下输出,表明数据一收到就自动发送到 MySQL 数据库。

...
The Redis data was sent to the MySQL database successfully.

要验证数据是否从Redis 通道成功路由到MySQL 数据库,请以“billingdbuser”身份登录MySQL 服务器。

$ mysql -u billing_db_user -p

输入您的 billing_db_user 密码(例如, EXAMPLE_PASSWORD) 并按 ENTER 继续。 然后,运行 USE 语句切换到 billing_db 数据库。

mysql> USE billing_db;

输出。

Database changed.

接下来,执行以下命令 SELECT 语句来检索记录。 要获取有关显示其姓名的客户账单的宝贵信息,请链接 billingscustomers 通过执行以下表格 JOIN 陈述。

mysql> SELECT
           billings.ref_id as bill_reference_no,
           customers.customer_id,
           customers.first_name,
           customers.last_name,
           billings.billing_period,
           billings.units_consumed,
           billings.cost_per_unit,
          CONCAT('$', (billings.units_consumed * billings.cost_per_unit)) as bill_amount
       FROM billings
       LEFT JOIN customers
       ON billings.customer_id = customers.customer_id;

您现在应该在表中看到所有已处理的客户账单,如下所示。

+-------------------+-------------+------------+-----------+----------------+----------------+---------------+-------------+
| bill_reference_no | customer_id | first_name | last_name | billing_period | units_consumed | cost_per_unit | bill_amount |
+-------------------+-------------+------------+-----------+----------------+----------------+---------------+-------------+
|                 1 |           1 | JOHN       | DOE       | 2021-08-31     |          12.36 |           2.5 | $30.9       |
|                 2 |           2 | MARY       | SMITH     | 2021-08-31     |           40.2 |           2.5 | $100.5      |
|                 3 |           3 | STEVE      | JONES     | 2021-08-31     |          24.36 |           2.5 | $60.9       |
+-------------------+-------------+------------+-----------+----------------+----------------+---------------+-------------+
3 rows in set (0.00 sec)

以上输出确认 Redis Pub/Sub 逻辑按预期工作。

结论

在本指南中,您已使用 Redis Pub/Sub 功能来 发布 水费账单数据 渠道 在带有 PHP 的 Ubuntu 20.04 服务器上。 你那时 订阅了 到通道处理并保存数据到 MySQL 数据库。 本指南向您展示了快速 Redis 内存数据库服务器在解耦系统方面的多功能性。

注:本教程在Vultr VPS上测试通过,如需部署请前往Vultr.com