今回は、PHP が同時繰り返し書き込みを防ぐ方法と、同時繰り返し書き込みを防ぐための PHP の 注意事項 を説明します。実際のケースを見てみましょう。
1. 前に書く:
サプライチェーンシステム全体では、書くためのインターフェースに関しては、さまざまな種類の文書が存在します。ドキュメント データ (追加、削除、変更操作) では、フロントエンドが関連する制限を実装している場合でも、ネットワークまたは異常な操作により同時に繰り返し呼び出しが発生し、同じドキュメントが同じ処理になる可能性があります。この状況がシステムに異常な影響を与えることを防ぐために、各リクエストはビジネス ロジックを実行する前にロックを取得する必要があり、実行が完了するとロックが解放されます。同じドキュメントの同時反復操作リクエストに対して 1 つのリクエストのみがロックを取得できることが保証されます (Redis に依存) シングルスレッド)、悲観的なロック設計です
注: Redis ロックは通常、同時反復リクエストを解決するためにのみ使用されます。私たちのシステムでは、非同時反復リクエストの場合、通常、データベースまたはログを使用してデータのステータスを確認し、2 つのメカニズムを組み合わせることでリンク全体の信頼性を確保できます。
2. ロックメカニズム:以下の実装は主に Redis setnx コマンドに依存します:
ただし、setnx の使用には問題があります。つまり、setnx コマンドは有効期限の設定をサポートしていません。ロック操作全体がアトミック操作にならないように、expired コマンドを使用して key に別のタイムアウトを設定する必要があります。setnx ロックは成功しても、異常終了によりタイムアウトが正常に設定されない可能性があります。プログラムのロックが時間内に解除されないと、デッドロックが発生する可能性があります (シーンにデッドロックが発生しない場合でも、無駄なキーをメモリ内に保持するのは良い設計ではありません)。
この状況は Redis トランザクションを使用して解決でき、setnx とexpired の 2 つの命令がアトミックな操作として実行されますが、この方法は比較的面倒です。幸いなことに、Redis 2.6.12 以降のバージョンでは、Redis set コマンドがサポートされています。 nx および ex モード、有効期限のアトミック設定をサポート:3. ロックの実装 (完全なテスト。コードは最後に掲載されます):
/** * 加单据锁 * @param int $intOrderId 单据ID * @param int $intExpireTime 锁过期时间(秒) * @return bool|int 加锁成功返回唯一锁ID,加锁失败返回false */ public static function addLock($intOrderId, $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME) { //参数校验 if (empty($intOrderId) || $intExpireTime <= 0) { return false; } //获取Redis连接 $objRedisConn = self::getRedisConn(); //生成唯一锁ID,解锁需持有此ID $intUniqueLockId = self::generateUniqueLockId(); //根据模板,结合单据ID,生成唯一Redis key(一般来说,单据ID在业务中系统中唯一的) $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); //加锁(通过Redis setnx指令实现,从Redis 2.6.12开始,通过set指令可选参数也可以实现setnx,同时可原子化地设置超时时间) $bolRes = $objRedisConn->set($strKey, $intUniqueLockId, ['nx', 'ex'=>$intExpireTime]); //加锁成功返回锁ID,加锁失败返回false return $bolRes ? $intUniqueLockId : $bolRes; }
Unlockingロック時に一意のロック ID を比較することを意味します。比較が成功すると、ロック解除プロセス全体でキーが削除されることに注意してください。これは、redis の監視とトランザクションの実装に依存します。
WATCH コマンドは、1 つ以上のキーを監視できます。キーの 1 つが変更 (または削除) されると、それ以降のトランザクションは実行されません。監視は EXEC コマンドまで継続します (トランザクション内のコマンドは EXEC 後に実行されるため、WATCH 監視のキー値は MULTI コマンドの後に変更できます)5. ロック解除の実装 (完全なテスト コードは最後に掲載されます) ):
/** * 解单据锁 * @param int $intOrderId 单据ID * @param int $intLockId 锁唯一ID * @return bool */ public static function releaseLock($intOrderId, $intLockId) { //参数校验 if (empty($intOrderId) || empty($intLockId)) { return false; } //获取Redis连接 $objRedisConn = self::getRedisConn(); //生成Redis key $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); //监听Redis key防止在【比对lock id】与【解锁事务执行过程中】被修改或删除,提交事务后会自动取消监控,其他情况需手动解除监控 $objRedisConn->watch($strKey); if ($intLockId == $objRedisConn->get($strKey)) { $objRedisConn->multi()->del($strKey)->exec(); return true; } $objRedisConn->unwatch(); return false; }
6. 全体的なテスト コードを添付します (このコードは単なる簡易バージョンです)
connect($strIp, $intPort); return $objRedis; } /** * 用于生成唯一的锁ID的redis key */ const REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id'; /** * 生成锁唯一ID(通过Redis incr指令实现简易版本,可结合日期、时间戳、取余、字符串填充、随机数等函数,生成指定位数唯一ID) * @return mixed */ public static function generateUniqueLockId() { return self::getRedisConn()->incr(self::REDIS_LOCK_UNIQUE_ID_KEY); } } //test $res1 = Lock_Service::addLock('666666'); var_dump($res1);//返回lock id,加锁成功 $res2 = Lock_Service::addLock('666666'); var_dump($res2);//false,加锁失败 $res3 = Lock_Service::releaseLock('666666', $res1); var_dump($res3);//true,解锁成功 $res4 = Lock_Service::releaseLock('666666', $res1); var_dump($res4);//false,解锁失败
この記事の事例を読んだ後は、この方法を習得したと思います。さらに興味深い情報については、お支払いください。 PHP 中国語 Web サイトの他の関連記事にも注目してください。 推奨書籍:
PHP で Web クラスターセッションの同期を設定する手順の詳細な説明 PHP で大きなファイルをカットおよびマージする手順の詳細な説明
以上がPHPでの同時繰り返し書き込みを防ぐ方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。