ダミヌのキャッシュ

「ティヌポット」の目を通しおキャッシュ









キャッシュは統合システムです。 したがっお、異なる角床では、結果は実領域ず虚領域の䞡方に存圚する可胜性がありたす。 埅っおいるものず本圓に持っおいるものの違いを理解するこずは非垞に重芁です。



状況を䞞ごずスクロヌルしおみたしょう。



Tl; drアヌキテクチャにキャッシュを远加するずき、キャッシュは負荷のかかったシステムを䞍安定にする手段になり埗るこずを明確に認識するこずが重芁です。 蚘事の最埌をご芧ください。



為替レヌトを返すデヌタベヌスにアクセスできるずしたす。 rates.example.com/?currency1=XXX&currency2=XXXに問い合わせるず、それに応じおコヌスのプレヌンテキスト倀を取埗したす。 たずえば、デヌタベヌスぞの1000ク゚リごずに1ナヌロセントかかりたす。



それで、私たちのりェブサむトでドルからナヌロぞの為替レヌトを衚瀺したいず思いたす。 これを行うには、コヌスを取埗する必芁がありたす。そのため、このサむトでは、䟿利に䜿甚できるAPIラッパヌを䜜成しおいたす。



たずえば、次のように



<?php //      :) function get_current_rate($currency1, $currency2) { $api_host = "http://rates.example.com/"; $args = http_build_query(array("currency1"=>$currency1, "currency2"=>$currency2)); $rate = @file_get_contents($api_host."?".$args); if ($rate === FALSE) { return $rate; } else { return (float) $rate; } }
      
      





そしお、適切な堎所のテンプレヌトに次のようなものを挿入したす。



  {{ get_current_rate("USD","EUR")|format(".2f") }} USD/EUR
      
      





たあ、あるいは<?=sprintf(".2f", get_current_rate("USD","EUR"))?>



ですが、これは最埌の䞖玀です。



Naive実装は、考えられる最も単玔なこずを行いたす。各ナヌザヌからリモヌトシステムを芁求し、その答えを盎接䜿甚したす。 これは、珟圚、私たちのペヌゞのナヌザヌによる1000回のビュヌごずに、さらに倚くの費甚がかかっおいるこずを意味したす。 それは芋えるだろう-ペニヌ。 しかし、プロゞェクトは成長しおいたす。毎日1,000人のナヌザヌが毎日サむトにアクセスし、それぞれ20ペヌゞを閲芧しおいたす。これは既に月額6ナヌロです。これにより、サむトは無料から最も安䟡な専甚仮想サヌバヌぞの支払いに完党に匹敵したす。



Ma䞋キャッシュ



䞀般に、この情報がそれほど頻繁に必芁ずされない堎合、ペヌゞの曎新ごずに各ナヌザヌのコヌスを尋ねる必芁があるのはなぜですか リフレッシュレヌトを、たずえば5秒に1回に制限しおみたしょう。 ペヌゞからペヌゞに移動するナヌザヌには新しい番号が衚瀺され、1000分の1の金額が支払われたす。



すぐに蚀っおやった いく぀かの行を远加したす。



 <?php function get_current_rate($currency1, $currency2) { $cache_key = "rate_".$currency1."_".$currency2; //   https://cloud.google.com/appengine/docs/php/memcache/ $memcache = new Memcache; $memcache->addServer("localhost", 11211); $rate = $memcache->get($cache_key); if ($rate) { return $rate; } else { $api_host = "http://rates.example.com/"; $args = http_build_query(array("currency1"=>$currency1, "currency2"=>$currency2)); $rate = @file_get_contents($api_host."?".$args); if ($rate === FALSE) { return $rate; } else { $memcache->set($cache_key, (float) $rate, 0, 5); return (float) $rate; } } }
      
      





これはキャッシュの最も重芁な偎面です。 最埌の結果を保存したす。



そしお出来䞊がり このサむトは再び無料になりたす...月末たで、倖郚システムから4ナヌロの口座を発芋するたで。 もちろん、6ではありたせんが、倧幅な節玄が期埅できたす。



幞いなこずに、倖郚システムを䜿甚するず、芋越し額を確認できたす。ここでは、出垭率のピヌク時に正確に5秒ごずに100以䞊の芁求のバヌストが衚瀺されたす。



これが、キャッシュの2番目の重芁な偎面であるク゚リ重耇排陀を導入した方法です。 実際のずころ、倀が叀くなったら、結果がキャッシュにあるかどうかを確認しおから新しい倀を保存するたでに、すべおの着信芁求は実際に倖郚システムぞの芁求を同時に実行したす。



memcacheの堎合、これは、たずえば次のように実装できたす。



 <?php function get_current_rate($currency1, $currency2) { $cache_key = "rate_".$currency1."_".$currency2; $memcache = new Memcache(); $memcache->addServer("localhost", 11211); while (true) { $rate = $memcache->get($cache_key); if ($rate == "?") { sleep(0.05); } else if ($rate) { return $rate; } else { //  ,     ,    if ($memcache->add($cache_key, "?", 0, 5)) { $api_host = "http://rates.example.com/"; $args = http_build_query(array("currency1"=>$currency1, "currency2"=>$currency2)); $rate = @file_get_contents($api_host."?".$args); if ($rate === FALSE) { return $rate; } else { $memcache->set($cache_key, (float) $rate, 0, 5); return (float) $rate; } } } } }
      
      





そしお最埌に、消費は予想ず同等でした-5秒で1リク゚スト、費甚は1か月あたり2ナヌロに削枛されたした。



なぜ2ですか 䜕千人もキャッシュなしで6぀ありたしたが、すべおをキャッシュしたしたが、3回しか枛少したせんでしたか はい、早めに蚈算する䟡倀がありたした... 5秒に1回= 1分あたり12 = 1時間あたり72 = 1か月あたり576 = 1か月あたり1侇7,000人で、誰もがスケゞュヌルに沿っおいるわけではなく、倜遅くに芋おいる奇劙な人がいたす...䜕癟ものコヌルの代わりに、ピヌクが1぀だけあり、静かな時間に、以前ず同様に、ほずんどすべおのコヌルのリク゚ストが通過したす。 それでも、最悪の堎合でも、スコアは31×86400÷5 = 5.36ナヌロである必芁がありたす。



そこで、別の偎面に出䌚いたした。キャッシュはを助けたすが、負荷を排陀したせん 。



しかし、私たちの堎合、人々はプロゞェクトに来お立ち去り、ある時点でブレヌキに぀いお䞍平を蚀い始めたす。数秒間ペヌゞがフリヌズしたす。 たた、午前䞭にサむトがたったく応答しない堎合がありたす... サむトのコン゜ヌルを衚瀺するず、午埌に远加のむンスタンスが起動されるこずがありたす。 同時に、ク゚リの実行速床はリク゚ストごずに5〜15秒に䜎䞋したす-これが起こるためです。



読者のための緎習前のコヌドを泚意深く芋お、理由を芋぀けおください。



はい、はい、はい。 もちろん、これはif ($rate === FALSE)



ブランチにありたす。 倖郚サヌビスが゚ラヌを返した堎合、ロックを解陀したせんでした...ずいう意味で「」 蚘録されたたたで、誰もがそれが陳腐化するのを埅っおいたす。 これは簡単に修正できたす。



  if ($rate === FALSE) { $memcache->delete($cache_key); return $rate; } else {
      
      





ちなみに、このレヌキは単なるキャッシュではなく、分散ロックの䞀般的な偎面です。デッドロックを回避するために、ロックを解陀しおタむムアりトを蚭定するこずが重芁です。 「」を远加した堎合 寿呜がないず、倖郚システムずの通信の最初の゚ラヌですべおがフリヌズしたす。 残念ながら、memcacheは分散ロックを䜜成するための良い方法を提䟛したせん。行レベルのロックを備えた本栌的なデヌタベヌスを䜿甚する方が優れおいたすが、それは単にこのレヌキを螏んだために必芁な叙情的な䜙談でした。



そのため、問題を修正したしたが、䜕も倉わっおいたせん。ブレヌキがただ時々起動したした。 泚目に倀するのは、圌らが技術システムに関する倖郚システムからのニュヌスレタヌず時間的に䞀臎したこずです...



さあさあ...ちょっず䌑憩しお、今集めたもの、キャッシュができるこずを振り返りたしょう。



  1. 最埌の既知の結果を芚えおおいおください。
  2. 結果がただたたは䞍明である堎合のク゚リの重耇排陀。
  3. ゚ラヌが発生した堎合に正しいロック解陀を確認しおください。


気づいた キャッシュは、゚ラヌの堎合にポむント1〜2を提䟛する必芁がありたす。 最初は、これは明らかなようです。䜕が起こったのか分からず、1぀の芁求が萜ち、次の芁求が曎新されたす。 しかし、次のものも゚ラヌを返すずどうなりたすか そしお次は 10件のリク゚ストがあり、最初のリク゚ストがロックを取埗し、結果を取埗しようずしたしたが、萜ちおしたいたした。 次のものはチェックしたす-そのため、ブロッキングも倀もありたせん。結果を求めたす。 巊折したした。 だから皆のために。 たあ、愚かなこず 良い10が来お、1぀が詊されたした-みんな萜ちたした。 そしお、次のものをもう䞀床詊しおみたしょう



そのため、キャッシュはしばらくの間吊定的な結果を保存できなければなりたせん。 単玔な初期の仮定は、本質的に0秒間ネガティブな結果を保存するこずを意味したすただし、この吊定を既に埅っおいるすべおの人に送信したす。 残念ながら、Memcacheの堎合、れロレむテンシの実装は非垞に問題がありたす厄介な読者の宿題ずしお残したす。アドバむス CASメカニズムを䜿甚したす。はい、AppEngineでMemcacheずMemcachedを䜿甚できたす。



負の倀を1秒の寿呜で保存するだけです。



 <?php function get_current_rate($currency1, $currency2) { $cache_key = "rate_".$currency1."_".$currency2; $memcache = new Memcache(); $memcache->addServer("localhost", 11211); while (true) { $flags = FALSE; $rate = $memcache->get($cache_key, $flags); if ($rate == "?") { sleep(0.05); } else if ($flags !== FALSE) { //   ,   ,   , //       ,  false. return $rate; } else { if ($memcache->add($cache_key, "?", 0, 5)) { $api_host = "http://rates.example.com/"; $args = http_build_query(array("currency1"=>$currency1, "currency2"=>$currency2)); $rate = @file_get_contents($api_host."?".$args); if ($rate === FALSE) { //          $memcache->set($cache_key, $rate, 0, 1); return $rate; } else { $memcache->set($cache_key, (float) $rate, 0, 5); return (float) $rate; } } } } }
      
      





今ではそれだけで 、萜ち着くこずができたすか どんなに。 私たちが成長しおいる間、私たちの最愛の倖郚サヌビスも成長しおいたした、そしおある時点で、それは時々遅くなり、䞀瞬でも反応し始めたした...そしお驚くべきこず-それずずもに、私たちのりェブサむトも遅くなり始めたした そしお再び皆のために しかし、なぜですか ゚ラヌが発生した堎合、すべおをキャッシュし、゚ラヌを蚘憶し、それにより、埅機䞭の党員を䞀床に解攟したすか



...そしおここにありたす。 もう䞀床コヌドをよく芋おください file_get_contents()



が蚱可する限り、倖郚システムぞのリク゚ストは実行されたす。 リク゚ストの実行時には他の党員が埅機しおいるため、キャッシュが廃止されるたびに、すべおのスレッドがメむンスレッドの実行を埅機し、到着したずきにのみ新しいデヌタを受信したす。



たあ、埅機する代わりに、 memcache->add



...の呚りの条件にelse{}



ブランチをmemcache->add



たす。本圓です。最埌の既知の倀を返す必芁がありたすよね 結局、新しい情報がない堎合、叀い情報を受信するこずに同意したこずを正確にキャッシュしたす。 そのため、もう1぀のキャッシュ芁件がありたす。芁求が1぀だけ遅くなるようにしたす。



すぐに蚀っおやった



 <?php function get_current_rate($currency1, $currency2) { $cache_key = "rate_".$currency1."_".$currency2; $memcache = new Memcache(); $memcache->addServer("localhost", 11211); while (true) { $flags = FALSE; $rate = $memcache->get($cache_key, $flags); if ($rate == "?") { sleep(0.05); } else if ($flags !== FALSE) { return $rate; } else { if ($memcache->add($cache_key, "?", 0, 5)) { $api_host = "http://rates.example.com/"; $args = http_build_query(array("currency1"=>$currency1, "currency2"=>$currency2)); $rate = @file_get_contents($api_host."?".$args); if ($rate === FALSE) { //      ,    //  .  ,   . $memcache->set($cache_key, $rate, 0, 1); return $rate; } else { //      _stale_ ,     // - : , ,    //    . $memcache->set("_stale_".$cache_key, (float) $rate); $memcache->set($cache_key, (float) $rate, 0, 5); return (float) $rate; } } else { //    ,      — //     ,      . //    ,   false,    . return $memcache->get("_stale_".$cache_key); } } } }
      
      





それで、私たちは再び勝ちたした。倖郚サヌビスが遅くなっおも、1ペヌゞしか遅くなりたせん...぀たり、平均応答時間が短瞮されたように、ナヌザヌはただ少し䞍満です。



泚通垞のPHPはデフォルトでセッションをファむルに曞き蟌み、同時リク゚ストをブロックしたす。 この動䜜を回避するには、read_and_closeパラメヌタヌをsession_startに枡すか、必芁なすべおの倉曎が行われた埌、session_closeでセッションを匷制的に閉じたす。そうしないず、1぀のペヌゞが遅くなるのではなく、1人のナヌザヌ倀を曎新するスクリプトが同じからの別の芁求によっおセッションの開始をブロックするためナヌザヌ。 AppEngineで実行されるず、memcacheのセッションストレヌゞはデフォルトで有効になりたす。぀たり、ロックなしであるため、問題はそれほど目立ちたせん。



したがっお、ナヌザヌはただ䞍満ですああ、これらのナヌザヌ。 サむトで最も時間を費やしおいる人は、これらの短いハングにただ気づいおいたす。 そしお、圌らはこれがめったに起こらないずいう事実にたったく満足しおおらず、単に幞運ではありたせん。 この堎合、芁件をさらに厳しくする必芁がありたす 。 芁求は応答を埅぀べきではありたせん 。



この質問の定匏化では䜕ができたすか できるこず



  1. 「 応答埌の実行 」トリックを実行しおみおください。぀たり、倀を曎新する必芁がある堎合は、スクリプトの残りの実行埌にこれを行うハンドラヌを登録したす。 アプリケヌションず実行環境に䟝存したす。 最も信頌できる方法はfastcgi_finish_request ()



    を䜿甚するこずfastcgi_finish_request ()



    。これにはphp- fastcgi_finish_request ()



    サヌバヌ構成が必芁ですしたがっお、AppEngineでは䜿甚できたせん。



  2. 個別のスレッドで曎新を行いたす぀たり、 pcntl_fork()



    実行するか、 system()



    たたは他の䜕かを介しおスクリプトを実行したす-再び、サヌバヌで機胜し、堎合によっおはセキュリティをあたり気にしない共有ホスティングサヌビスでも機胜したす。しかし、もちろん、劄想的なセキュリティのサヌビスでは機胜したせん。぀たり、AppEngineは適切ではありたせん。



  3. キャッシュを曎新するために垞に実行されおいるバックグラりンドプロセスがありたす。プロセスは、キャッシュ内の倀が叀くなっおいるかどうかを所定の頻床で確認する必芁がありたす。たた、存続期間が終了し、キャッシュの存続䞭に倀が必芁になった堎合、曎新したす。 この点に぀いおは、為替レヌトで貧匱なサむトにうんざりし、より楜しいものに移るずきに少し埌で説明したす。


実際のずころ、デヌタを垞にホットな状態に維持するこずは、ほんの数行のPHPコヌドよりも少し耇雑なタスクです。そのため、単玔なケヌスでは、ある皮のリク゚ストが定期的に「考え盎される」ずいう事実に耐える必芁がありたすランダムではなく、いく぀か;぀たり、 randomではなく、 任意です。 タスクを詊すには、このアプロヌチの適甚性が垞に重芁です



そのため、デヌタプロバむダヌは成長しおいたすが、すべおの顧客がハブを読み取るわけではないため、正しいキャッシングを䜿甚せずたったく䜿甚しない堎合、ある時点で膚倧な数の芁求を発行し始めたす。圌はゆっくりずだけでなく、 非垞にゆっくりず答え始めたす。 数十秒以䞊。 もちろん、ナヌザヌはF5キヌを抌すかペヌゞをリロヌドできるこずをすぐに発芋し、すぐに衚瀺されたす-倖郚の応答を単に埅っおいるがリ゜ヌスを消費するプロセスがハングし始めたため、ペヌゞのみが再び無料の制限に達し始めたした。



他の副䜜甚の䞭でも、時代遅れのコヌスを瀺すケヌスがより頻繁になっおいたす。 [うヌん...䞀般的に、私たちのケヌスに぀いおではなく、陳腐化が肉県で芋えるより耇雑なものに぀いお話しおいるず想像しおください:)実際には、単玔な堎合でも、そのような完党に明癜な劚害に気付くナヌザヌがいるはずです]。

䜕が起こるか芋おください



  1. リク゚スト1が来たした。キャッシュにデヌタがないため、トヌクン「」を远加したした。 5秒間、コヌスに行きたした。
  2. 1秒埌、リク゚スト番号2が届き、マヌカヌ「」が衚瀺され、叀いレコヌドからデヌタが返されたした。
  3. 3秒埌、リク゚スト番号3が到着し、マヌカヌ「」が衚瀺され、叀い状態が返されたした。
  4. 1秒埌、マヌカヌ「」 芁求1がただ応答を埅機しおいる堎合でも非掚奚です。
  5. さらに2秒埌、リク゚スト番号4が到着し、マヌカヌはありたせん。新しいマヌカヌを远加しお、コヌスに進みたす。
  6. ...
  7. 芁求1が応答を受信し、結果を保存したした。
  8. リク゚ストXが到着し、最初の質問のキャッシュから珟圚の回答を受け取りたしたそしお、い぀その回答を埗たしたかリク゚ストの時点で、たたは回答の瞬間に-誰も知りたせん...。
  9. リク゚スト番号4は回答を受け取り、結果を保存したした-そしお、この回答がより新しいか叀いかは明確ではありたせん...


もちろん、ここではstream_context_create



ini_set("default_socket_timeout")



䜿甚しお必芁なタむムアりトを蚭定するか、 stream_context_create



䜿甚するstream_context_create



たす。そのため、別の重芁な偎面がありたす 。 動䜜に察する䞀般的な解決策はありたせんが、原則ずしお、キャッシュ時間は蚈算時間より長くする必芁がありたす。 蚈算時間がキャッシュの有効期間を超える堎合、 キャッシュは適甚されたせん 。 これはもはやキャッシュではなく、信頌できるストレヌゞに保存されるべき事前蚈算です。



それでは、小蚈を芁玄したしょう。 垞識では、キャッシュは次のこずを行いたす。



  1. 既知の回答に察するほずんどの芁求を眮き換えたす。
  2. 高䟡なデヌタのリク゚スト数を制限したす。
  3. ク゚リ時間をナヌザヌに芋えなくしたす。


ただし、実際には



  1. キャッシュラむフりィンドりからの䞀郚のリク゚ストを保存された倀に眮き換えたすたずえば、メモリ䞍足や莅沢なリク゚ストなどにより、キャッシュはい぀でも倱われる可胜性がありたす。
  2. 芁求の数を制限しようずしおいたすただし、発信芁求の頻床の制限を特別に実装しなければ、「䞀床に最倧1぀の発信芁求」タむプの特性のみを提䟛するこずが実際に可胜です。
  3. ク゚リの実行時間は䞀郚のナヌザヌにのみ衚瀺されたすさらに、「幞運なナヌザヌ」は決しお均等に分散されおいたせん。


キャッシュは、保存されたデヌタの「䞀時性」を意味するため、キャッシングシステムは、䞀般的にラむフタむムずデヌタを保存するリク゚ストの事実を自由に凊理できたす。





したがっお、単にキャッシュを適甚するだけで、保留䞭のアクションの鉱山を眮くこずがありたすが、これは確かに爆発したす-しかし、今ではなく、将来、決定にはるかに費甚がかかりたす。 システムのパフォヌマンスを蚈算するずきは、肯定的な応答をキャッシュするこずによる実行時間の短瞮を考慮せずに考慮するこずが重芁です。



最も単玔なケヌスを考えおみたしょう。





たた、負荷が高い堎合、キャッシュはポむズニングされ、リク゚ストは5秒間キャッシュされたせんが、1秒間のみキャッシュされたす。぀たり、2぀のリク゚ストが垞にビゞヌ状態になりたす最初のリク゚ストを実行し、 、メンテナンスの残りの容量は1秒あたり60リク゚ストに削枛されたす。 ぀たり、1分あたり6000リク゚スト平均に基づくの有効容量は、3600に急萜したす。 ぀たり、毎分5,000リク゚ストでポむズニングが発生するず、負荷が5,000から3,000に䜎䞋するたで、システムは䞍安定になりたす。 ぀たり、トラフィックの急増は、長期的なシステムの䞍安定を匕き起こす可胜性がありたす。



これは、新機胜を備えたニュヌスレタヌの埌に、ナヌザヌの波がほが同時に届く堎合に、特に玠晎らしく芋えたす。 定期的なマヌケティングhabraeffectの䞀皮。



これはすべお、キャッシュを䜿甚できない、たたは䜿甚するこずが有害であるこずを意味するものではありたせん , , , .



All Articles