遅延キューは、その名のとおり、遅延機能を備えたメッセージキューです。では、どのような状況でそのようなキューが必要になるのでしょうか?
1. 背景
ビジネス シナリオを見てみましょう:
1. メンバーシップの有効期限が切れる 3 日前にリコール通知を送信します
2. 注文の支払いが完了したら、5 分後にダウンストリーム リンクが正常であるかどうかを確認します (たとえば、ユーザーがメンバーシップを購入した後、さまざまなメンバーシップ ステータスが正常に設定されているかどうかなど)。返金ステータスの注文が正常に返金されたかどうかを定期的に確認するにはどうすればよいですか?
4. 通知が失敗した場合、相手が応答するまで 1 分、3 分、5 分、7 分後に通知が繰り返されますか?
通常、上記の問題を解決する最も簡単かつ直接的な方法は、メーターを定期的にスキャンすることです。
テーブル スキャンの問題点は次のとおりです:
1. テーブル スキャンはデータベースに長時間接続されます。大量の場合、接続は異常な中断を起こしやすくなります。より多くの例外処理とプログラムが必要になる 高い堅牢性要件
2. データ量が多いと遅延が大きくなり、規定内に処理が完了できずビジネスに影響を与える。処理を開始すると、追加のメンテナンスコストが発生し、根本的な解決にはなりません。
3. 各企業は独自のテーブル スキャン ロジックを維持する必要があります。ビジネスが増加すると、テーブル スキャン部分のロジックが繰り返し開発されることがわかりますが、それは非常によく似ています。
遅延キューは上記のニーズを非常によく解決できます
2. 調査次のように、市場にあるいくつかのオープン ソース ソリューションを調査しました:
Youzan Technology: 原則のみで、オープン ソース コードはなし
·Github Personal: https://github.com/ouqiang/lay-queue 1. Redis 実装に基づいて、構成できる Redis は 1 つだけです。ハングするとサービス全体が利用できなくなる ユーザビリティが悪い
2. コンシューマー側はプルモデルを実装しており、アクセスコストが高い プロジェクトごとにアクセスコードを実装する必要がある
3. starを使っている人が少ない、本番環境に置くとリスクがある、またGo言語が分からないと何か問題が起きた時のメンテナンスが難しい
#·
SchedulerX-Alibaba オープン ソース: 非常に強力ですが、コンポーネントに依存する複雑な操作とメンテナンス 多すぎて軽量ではありません·
RabbitMQ 遅延タスク: あります遅延関数自体がないため、機能を利用して独自に実装する必要があり、会社はこのキューをデプロイしていないため、これを個別にデプロイします。遅延キューを作成するコストは少し高く、特別な運用と保守も必要です。現在、チームはサポートしていません。基本的には上記の理由から、自分で書く予定です。私は普段 PHP を主に使用しており、プロジェクトは基本的に redis の zset 構造をストレージとして使用します。実装されています。」 PHP 言語では、実装原則は Youzan チームを参照しています: https://tech.youzan.com/queuing_lay/関連する推奨事項: 「php チュートリアル
」·軽量: swoole、workman などのネットワーク フレームワークを導入する必要がなく、少ない PHP 拡張機能で直接実行できます。
安定性: マスターワーク アーキテクチャを使用すると、マスターはビジネス処理を行わず、子プロセスの管理のみを担当します。子プロセスが異常終了すると、自動的に起動
·可用性:
1. マルチインスタンス展開をサポートし、各インスタンスはステートレスであり、1 つのインスタンスの障害はサービスに影響しません2. 複数の Redis の構成をサポートし、1 つの Redis 電話を切ると一部のメッセージにのみ影響します
3. ビジネス側は簡単にアクセスできますバックグラウンドで、関連するメッセージ タイプを入力するだけで済みます #拡張性: 消費時プロセスにボトルネックがある場合、消費プロセスの数を増やすように構成できます。書き込みにボトルネックがある場合は、書き込みパフォーマンスは線形に改善できます。·リアルタイム: 一定時間の時間誤差は許容されます。
#サポート メッセージ削除: ビジネス ユーザーはいつでも指定されたメッセージを削除できます。
#メッセージ送信の信頼性: メッセージが遅延キューに入った後、少なくとも 1 回は消費されることが保証されます。
#書き込みパフォーマンス: qps>1000
4. アーキテクチャ設計と説明
全体的なアーキテクチャここに図の説明を挿入
マスターワーク アーキテクチャ モデルを採用しており、主に 6 つのモジュールが含まれています: 1.dq-mster: 子プロセスの作成と破棄の管理を担当するメイン プロセスリサイクルとシグナル通知2.dq-server: メッセージの書き込み、読み取り、削除機能と、redis 接続プールの維持を担当します3.dq-timer-N: からの zset を担当します。 redis 構造内の期限切れメッセージをスキャンし、準備完了キューへの書き込みを担当します。zset 構造内のメッセージは時間順に並べられているため、通常は 2 で十分です。数値は構成可能です。4.dq-consume -N : レディキューからメッセージを読み取り、対応するコールバックインターフェイスに通知します。番号は設定可能です。 5.dq-redis-checker: Redis のサービスステータスを確認します。Redis がダウンしている場合は、 、アラーム電子メールを送信します 6.dq-http-server: トピックを登録するための Web バックグラウンド インターフェイスを提供します5. デプロイメント
環境の依存関係: PHP 5.4 インストール ソケット、redis、pcntl、pdo_mysql 拡張機能step1:安装数据库用于存储一些topic以及告警信息
create database dq; #存放告警信息 CREATE TABLE `dq_alert` ( `id` int(11) NOT NULL AUTO_INCREMENT, `host` varchar(255) NOT NULL DEFAULT '', `port` int(11) NOT NULL DEFAULT '0', `user` varchar(255) NOT NULL DEFAULT '', `pwd` varchar(255) NOT NULL DEFAULT '', `ext` varchar(2048) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; #存放redis信息 CREATE TABLE `dq_redis` ( `id` int(11) NOT NULL AUTO_INCREMENT, `t_name` varchar(200) NOT NULL DEFAULT '', `t_content` varchar(2048) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8; #存储注册信息 CREATE TABLE `dq_topic` ( `id` int(11) NOT NULL AUTO_INCREMENT, `t_name` varchar(1024) NOT NULL DEFAULT '', `delay` int(11) NOT NULL DEFAULT '0', `callback` varchar(1024) NOT NULL DEFAULT '', `timeout` int(11) NOT NULL DEFAULT '3000', `email` varchar(1024) NOT NULL DEFAULT '', `topic` varchar(255) NOT NULL DEFAULT '', `createor` varchar(1024) NOT NULL DEFAULT '', `status` tinyint(4) NOT NULL DEFAULT '1', `method` varchar(32) NOT NULL DEFAULT 'GET', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
step2:在DqConfg.文件中配置数据库信息: DqConf::$db
step3:启动http服务
在DqConf.php文件中修改php了路径 $logPath
命令:
php DqHttpServer.php --port 8088
访问:http://127.0.0.1:8088,出现配置界面
在这里插入图片描述
redis信息格式:host:post:auth 比如 127.0.0.1:6379:12345
stop4:启动服务进程
php DqInit.php --port 6789
看到如下信息说明启动成功
在这里插入图片描述
stop5:配置告警信息(比如redis宕机)
在这里插入图片描述
stop6:注册topic
在这里插入图片描述
在这里插入图片描述
step7:写入数据,在项目根目录下新建test.php文件写入
<?php include_once 'DqLoader.php'; date_default_timezone_set("PRC"); //可配置多个 $server=array( '127.0.0.1:6789', ); $dqClient = new DqClient(); $dqClient->addServer($server); $topic ='order_openvip_checker'; //topic在后台注册 $id = uniqid(); $data=array( 'id'=>$id, 'body'=>array( 'a'=>1, 'b'=>2, 'c'=>3, 'ext'=>str_repeat('a',64), ), //可选,设置后以这个通知时间为准,默认延时时间在注册topic的时候指定 'fix_time'=>date('Y-m-d 23:50:50'), ); //添加 $boolRet = $dqClient->add($topic, $data); echo 'add耗时:'.(msectime() - $time)."ms\n"; //查询 $time = msectime(); $result = $dqClient->get($topic, $id); echo 'get耗时:'.(msectime() - $time)."ms\n"; //删除 $time = msectime(); $boolRet = $dqClient->del($topic,$id); echo 'del耗时:'.(msectime() - $time)."ms\n";
执行php test.php
step8:查看日志
默认日志目录在项目目录的logs目录下,在DqConf.php修改$logPath
1.请求日志:request_ymd.txt
2.通知日志:notify_ymd.txt
3.错误日志:err_ymd.txt
step9:如果配置文件有改动
1.系统会自动检测配置文件新,如果有改动,会自动退出(没有找到较好的热更新的方案),需要重启,可以在crontab里面建个任务,1分钟执行一次,程序有check_self的判断
2.优雅退出命令: master检测侦听了USR2信号,收到信号后会通知所有子进程,子进程完成当前任务后会自动退出
ps -ef | grep dq-master| grep -v grep | head -n 1 | awk '{print $2}' | xargs kill -USR2
六、性能测试
需要安装pthreads拓展:
测试原理:使用多线程模拟并发,在1s内能成功返回请求成功的个数
php DqBench concurrency requests concurrency:并发数 requests: 每个并发产生的请求数 测试环境:内存 8G ,8核cpu,2个redis和1个dq-server 部署在一个机器上,数据包64字节 qps:2400
七、值得一提的性能优化点:
1.redis multi命令:将多个对redis的操作打包成一个减少网络开销。
2.计数的操作异步处理,在异步逻辑里面用函数的static变量来保存,当写入redis成功后释放static变量,可以在redis出现异常时计数仍能保持一致,除非进程退出。
3.内存泄露检测有必要: 所有的内存分配在底层都是调用了brk或者mmap,只要程序只有大量brk或者mmap的系统调用,内存泄露可能性非常高 ,检测命令: strace -c -p pid | grep 'mmap| brk'。
4.检测程序的系统调用情况:strace -c -p pid ,发现某个系统函数调用是其他的数倍,可能大概率程序存在问题。
八、异常处理
如果调用通知接口在超时时间内,没有收到回复认为通知失败,系统会重新把数据放入队列,重新通知,系统默认最大通知10次(可以在Dqconf.php文件中修改$notify_exp_nums)通知间隔为2n+1,比如第一次1分钟,通知失败,第二次3分钟后,直到收到回复,超出最大通知次数后系统自动丢弃,同时发邮件通知。
ps:网络抖动在所难免,通知接口如果涉及到核心的服务,一定要保证幂等!!
九、线上情况
线上部署了两个实例每个机房部一个,4个redis作存储,服务稳定运行数月,各项指标均符合预期。
主要接入业务:
·订单10分钟召回通知
·接口超时或者失败补偿
项目地址: https://github.com/chenlinzhong/php-delayqueue
以上がPHP 遅延キューはどのように実装されますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。