ホームページ >バックエンド開発 >PHPチュートリアル >PHPでの高い同時実行性を解決する方法
この記事では主に、PHP での同時実行性の高さを解決する方法について説明します。飛び込み購入、フラッシュセール、抽選、チケット獲得などのアクティビティに関しては、過剰販売を避けるために在庫数量が制限されています。同時に注文する人の数が在庫数量を超えると、売れすぎの問題が発生します。では、この問題をどのように解決すればよいでしょうか? 私の考えは次のとおりです (疑似コード):
sql1: 製品在庫のクエリ
if(库存数量 > 0) { //生成订单... sql2:同时库存-1 }
同時実行がない場合、上記のプロセスは正常に見えます。2 人が同時にログインしていると仮定します。注文しましたが、在庫は 1 つしかありませんでした。sql1 の段階では、2 人がクエリした在庫は 0 を超えていました。そのため、最終的に sql2 を実行したところ、在庫は -1 になり、これは売られすぎた結果ではありません。私たちはバーが欲しいです。
この問題を解決するための一般的なアイデアをまとめました。
1. 追加の単一プロセスを使用してキューを処理し、注文リクエストをキューに入れて 1 つずつ処理するため、同時実行の問題は発生しません。ただし、バックグラウンドプロセスの開始と遅延の問題については、当面はここでは考慮しません。ここではメッセージキューを使用できます。Memcacheq と Radis をよく使用します。 たとえば、ユーザーが取得できるチケットが 100 個ある場合、これらの 100 個のチケットをキャッシュに置き、読み取りおよび書き込み時にロックしないようにできます。 同時実行の量が多い場合、約 500 人がチケットを取得できる可能性があるため、500 人以降のリクエストはイベントの終了時に静的ページに直接転送されます。 500人中400人が商品を手に入れることは不可能です。そのため、列に並んだ順に先着100名のみが正常に購入できます。次の 400 名はイベント終了ページに直接移動します。もちろん、「500 人」と入力するのは単なる例です。その数は自分で調整できます。アクティビティ終了ページでは、データベースではなく静的ページを使用する必要があります。これにより、データベースへの負担が軽減されます。
2.MySQL 楽観的ロックとは、たとえば、総在庫が 2 で、急ぎ購入イベントが送信されるとすぐに在庫が +1 になり、この時点で在庫が 3 になり、その後注文が生成された後、在庫を更新する前に、在庫が再度クエリされます (注文が生成されるため、もちろん在庫は -1 ですが、心配しないでください。在庫を再度確認すると、結果は 3 になります)。それが予想される在庫数量と一致しているかどうかを確認します。 (ここでの予想されるインベントリは 3 です)。一貫性がない場合はロールバックされ、インベントリが不十分であることをユーザーに通知します。ここでは、悲観的ロックについて説明します。じゃあ、楽観的ロックがあるはずだと思う人もいるかもしれません?? ここで、私が知っている悲観的ロックと楽観的ロックについて簡単に説明します
悲観的ロックと楽観的ロックは、2 つの一般的なリソース同時実行ロックです。設計のアイデアは、同時プログラミングにおける非常に基本的な概念でもあります。この記事では、データベース データに対するこれら 2 つの一般的なロック メカニズムの実装について、比較的かつ体系的に紹介します。
悲観的ロックの特徴は、最初にロックを取得してからビジネス操作を実行することです。つまり、「悲観的」はロックの取得が失敗する可能性が高いと考えているため、最初にロックが取得されたことを確認する必要があります。業務を行う前に正常に完了してください。一般に「1 つのロック、2 つのチェック、3 つの更新」と呼ばれるものは、悲観的なロックの使用を指します。一般に、データベースの悲観的ロックにはデータベース自体からのサポートが必要です。つまり、悲観的ロックは、一般的に使用される更新操作の選択を通じて実装されます。データベースが更新のための選択を実行すると、選択内のデータ行の行ロックが取得されるため、同時に実行される他の選択は 更新で同じ行を選択しようとすると、排他が発生し (行ロックが解放されるまで待つ必要があります)、ロック効果が得られます。 select for update によって取得された行ロックは、現在のトランザクションの終了時に自動的に解放されるため、トランザクション内で使用する必要があります。
ここで注意すべき点は、データベースによって更新の選択の実装とサポートが異なることです。たとえば、Oracle は待機なしの更新の選択をサポートしています。これは、ロックを取得できない場合、代わりにエラーがすぐに報告されることを意味します。 MySQL には待機オプションはありません。 MySQL のもう 1 つの問題は、select for update ステートメントの実行中にスキャンされたすべての行がロックされ、問題が発生しやすいことです。したがって、mysql で悲観的ロックを使用する場合は、テーブル全体のスキャンではなくインデックスを必ず使用してください。
楽観的ロックの特徴: 業務を優先し、必要な場合以外はロックを取得しないでください。つまり、私たちは「楽観的」であり、ロックが成功する可能性が最も高いと信じているため、ビジネス操作の完了後に実際にデータを更新する最後のステップの後にロックを取得するだけです。
データベースに対する楽観的ロックの実装は完全に論理的であり、データベースからの特別なサポートは必要ありません。一般的なアプローチは、ロックする必要があるデータにバージョン番号またはタイムスタンプを追加し、それを次のように実装することです:
1. SELECT data AS old_data, version AS old_version FROM …;2. 根据获取的数据进行业务操作,得到new_data和new_version3. UPDATE SET data = new_data, version = new_version WHERE version = old_versionif (updated row > 0) { // 乐观锁获取成功,操作完成 } else { // 乐观锁获取失败,回滚并重试 }
乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。好吧,在此唠叨总结下这两个锁:
乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能
乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方
3.根据update结果来判断,我们可以在sql2的时候加一个判断条件update table set 库存=xxx where 库存>0,如果返回false,则说明库存不足,并回滚事务。
4.借助文件排他锁,在处理下单请求的时候,用flock锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户"服务器繁忙"
大致代码如下:
阻塞(等待)模式
<?php$fp = fopen("lock.txt", "w+");if(flock($fp,LOCK_EX)) //锁定当前指针,,,{ //..处理订单 flock($fp,LOCK_UN); }fclose($fp);?>
非阻塞模式
<?php$fp = fopen("lock.txt", "w+");if(flock($fp,LOCK_EX | LOCK_NB)) { //..处理订单 flock($fp,LOCK_UN); }else{ echo "系统繁忙,请稍后再试"; } fclose($fp);?>
5.如果是分布式集群服务器,就需要一个或多个队列服务器 小米和淘宝的抢购还是有稍许不同的,小米重在抢的那瞬间,抢到了名额,就是你的,你就可以下单结算。而淘宝则重在付款的时候的过滤,做了多层过滤,比如要卖10件商品,他会让大于10的用户抢到,在付款的时候再进行并发过滤,一层层的减少一瞬间的并发量。
6.使用redis锁 product_lock_key 为票锁key 当product_key存在于redis中时,所有用户都可以进入下单流程。 当进入支付流程时,首先往redis存放sadd(product_lock_key, “1″),如果返回成功,进入支付流程。如果不成,则说明已经有人进入支付流程,则线程等待N秒,递归执行sadd操作。
当然类似于淘宝双11的疯抢架构远远比我说滴这些复杂多啦....更多解决方案需要不停滴去实战中获取心得....大家有好的解决思路清随时共享留言哈。
相关推荐:
以上がPHPでの高い同時実行性を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。