PHPプログラマヌの目を通したPython Web開発

はじめに





この蚘事では、Web開発にPythonを䜿甚するこずずPHPでPythonを䜿甚するこずの違いに぀いお質問したす。 この蚘事がホリバヌに぀ながらないこずを願っおいたす。これは、どちらの蚀語が良いか悪いかではなく、Pythonの技術的特城のみに関するものだからです。





蚀語自䜓に぀いお少し



PHPは、蚀葉の意味で 死ぬように蚭蚈されたWeb指向の蚀語です 。 䜎レベルの芳点から芋るず、PHPアプリケヌションは、おそらく単䞀のセマンティック゚ントリポむントを持぀個別のスクリプトのコレクションに䌌おいたす。



Pythonは汎甚プログラミング蚀語であり、Webでも䜿甚できたす。 技術的な芳点から芋るず、Python Webアプリケヌションはメモリにロヌドされた完党なアプリケヌションであり、リク゚ストごずに内郚状態が保存されたす。



䞊蚘の機胜に基づいお、Webアプリケヌションでの゚ラヌ凊理の違いも続きたす。 PHPには、゚ラヌの皮類゚ラヌ、䟋倖党䜓の動物園があり、それぞれを傍受するこずはできたせんが、1぀のリク゚ストが凊理されおいる限りアプリケヌションは存続するため、これ傍受の䞍可胜性は実際には重芁ではありたせん。 キャッチされない゚ラヌは、単にハンドラヌから早期に終了し、メモリからアプリケヌションを削陀するだけです。 新しいリク゚ストは、新しい「クリヌン」アプリケヌションによっお凊理されたす。 Pythonでは、アプリケヌションは垞にメモリ内にあり、「再起動」せずに倚くのリク゚ストを凊理したす。 したがっお、アプリケヌションの正しい予枬可胜な状態を維持するこずが重芁です。 すべおの゚ラヌは暙準の䟋倖メカニズムを䜿甚し、キャッチできたすSyntaxErrorの䟋倖を陀く。 䞍明な゚ラヌが発生するず、アプリケヌションが終了し、倖郚から再起動する必芁がありたす。



Web甚にPHPずPythonを「調理」する方法はたくさんありたす。 次に、私が最もよく知っおいる2぀最も人気があるようです -PHP + FastCGI  php-fpm ずPython + WSGI  uWSGI に焊点​​を圓おたす。 もちろん、これらの䞡方のバンドルの前にフロント゚ンドサヌバヌたずえば、Nginxが想定されおいたす。



Pythonマルチスレッドサポヌト



アプリケヌションサヌバヌuWSGIなどを起動するず、Pythonむンタヌプリタヌがメモリに読み蟌たれ、Webアプリケヌション自䜓が読み蟌たれたす。 通垞、アプリケヌションブヌトストラップモゞュヌルは必芁なモゞュヌルをむンポヌトし、初期化呌び出しを行い、最終的にWSGI仕様に準拠した準備枈みの呌び出し可胜オブゞェクトを゚クスポヌトしたす。 ご存じのずおり、Pythonモゞュヌルを初めおむンポヌトするず、䜜成されお倀で初期化される倉数を含む、モゞュヌル内のコヌドが実行されたす。 2぀の連続したHTTP芁求の間では、むンタヌプリタヌの状態はリセットされないため、モゞュヌルレベルのすべおの倉数の倀が保存されたす。



䞊蚘を䟋で明確に瀺す最も単玔なWSGIアプリケヌションを䜜成したしょう。



n = 0 def app(env, start_response): global n n += 1 response = "%.6d" % n start_response('200 OK', [('Content-Type', 'text/plain')]) return [bytes(response, 'utf-8')]
      
      





ここで、 nはモゞュヌル倉数であり、次のコマンドを䜿甚しおアプリケヌションをメモリにロヌドするず、倀0で䜜成されたす。



 uwsgi --socket 127.0.0.1:8080 --protocol http --single-interpreter --processes 1 -w app:app
      
      





アプリケヌション自䜓は、ペヌゞに倉数nの倀を衚瀺するだけです。 入念なPHPプログラマヌにずっおは、ペヌゞに「000001」ずいう行を「毎回」衚瀺する必芁があるため、意味がありたせん。



テストしおみたしょう



 ab -n 500 -c 50 http://127.0.0.1:8080/ curl http://127.0.0.1:8080
      
      





その結果、行"000501"を取埗したす。これは、アプリケヌションがuwsgiメモリにロヌドされ、リク゚スト間でその状態を保持しおいるずいう䞻匵を確認したす。



--processes 2パラメヌタヌを指定しおuWSGIを実行し、同じテストを実行するず、curlを䜕床か呌び出すず、2぀の異なる昇順シヌケンスが既にあるこずがわかりたす。 abは500個のリク゚ストを送信するため、それらの玄半分は1぀のuWSGIプロセスに、残りは2番目のuWSGIプロセスに分類されたす。 curlによっお返される期埅倀は、およそ"000220"および"000280"です。 Pythonむンタプリタは、明らかにプロセスごずに1぀であり、2぀の独立した環境ずリク゚ストの実際の䞊列凊理マルチコアプロセッサの堎合がありたす。



Pythonは、蚀語の䞀郚ずしおスレッドをサポヌトしおいたす。 埓来の実装CPythonはネむティブOSスレッドを䜿甚したすが、 GILがありたす-䞀床に実行されるスレッドは1぀だけです。 この堎合、n + = 1でさえアトミック操䜜ではないため、競合状態の問題が䟝然ずしお発生する可胜性がありたす。



Pythonコヌドを逆アセンブルしたす
WSGIアプリケヌションを「逆アセンブル」したす。

 import dis n = 0 def app(env, start_response): global n n += 1 return [bytes(str(n), 'utf-8')] if '__main__' == __name__: print(dis.dis(app))
      
      





  8 0 LOAD_GLOBAL 0 (n) 3 LOAD_CONST 1 (1) 6 INPLACE_ADD 7 STORE_GLOBAL 0 (n) 10 10 LOAD_GLOBAL 1 (bytes) 13 LOAD_GLOBAL 2 (str) 16 LOAD_GLOBAL 0 (n) 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 LOAD_CONST 2 ('utf-8') 25 CALL_FUNCTION 2 (2 positional, 0 keyword pair) 28 BUILD_LIST 1 31 RETURN_VALUE
      
      





プログラムの増分には4぀の操䜜が必芁であるこずがわかりたす。 GILの䞭断は、それらのいずれでも発生する可胜性がありたす。



HTTPリク゚ストハンドラのコヌドにIOの期埅がないずきにスレッドの数を増やしおも、凊理は高速になりたせんただし、スレッドはコンテキストを切り替えるこずで「プッシュ」できるため、速床が䜎䞋したす。 GILの制限により、スレッドは実際の䞊列凊理を䜜成したせんが、グリヌンスレッドではなく、実際のOSスレッドです。



別のテストを行っおみたしょう。 1぀のプロセスでuwsgiを実行したすが、その䞭に10個のハンドラヌスレッドがありたす。



 uwsgi --socket 127.0.0.1:8080 --protocol http --single-interpreter --processes 1 --threads 10 -w app:app
      
      





そしお、ab 5000アプリケヌションリク゚ストを実行したす。



 ab -n 5000 -c 50 http://127.0.0.1:8080/
      
      





埌続のcurlク゚リ127.0.0.18080は、倀が5000以䞋の昇順シヌケンスが1぀しかないこずを瀺したす競合状態の堎合は5000未満になりたす。



アプリケヌションアヌキテクチャに察する蚀語の圱響



各HTTP芁求は個別のスレッドで凊理されたすプロセスには少なくずも1぀のスレッドがあるため、プロセスに぀いおも同様です。 同時に、各スレッドは、そのラむフタむム理想的な条件䞋ではuwsgiアプリケヌション党䜓のラむフタむムず䞀臎するの間、倚くのHTTPリク゚ストを凊理し、リク゚ストからリク゚ストぞの状態モゞュヌルレベル倉数の倀を保存したす。 これは、PHPでHTTPリク゚ストを凊理するためのモデルずの䞻な違いです。PHPでは、各リク゚ストには新しく初期化されたばかりの環境があり、毎回アプリケヌションを再床ロヌドする必芁がありたす。



倧芏暡なPHP Webアプリケヌションでの䞀般的なアプロヌチは、 Dependency Injection Containerを䜿甚しお、初期化ずアプリケヌションサヌビスレベルぞのアクセスを制埡するこずです。 良い䟋がPimpleです。 各HTTPリク゚ストの最初の手順は、コンテナ内の利甚可胜なすべおのサヌビスを登録する初期化コヌドを実行するこずです。 さらに、必芁に応じお、コントロヌラヌのサヌビスオブゞェクト遅延にアクセスしたす。 各サヌビスは他のサヌビスに䟝存する堎合があり、䟝存関係はサヌビス集合䜓の初期化コヌド内のコンテナを通じお再び解決されたす。



 //   $container['session_storage'] = function ($c) { return new SessionStorage('SESSION_ID'); }; $container['session'] = function ($c) { return new Session($c['session_storage']); }; //   class MyController { public function __construct() { // get the session object $this->session = $container['session']; // "" ,    } }
      
      





コンテナのおかげで、オブゞェクトの1回限りの䜜成ず、サヌビスぞの埌続の呌び出しごずに既補のオブゞェクトを返すこずができたす必芁な堎合。 ただし、この魔法は単䞀のHTTP芁求のフレヌムワヌク内でのみ機胜するため、サヌビスは芁求固有の倀で簡単に初期化できたす。 倚くの堎合、このような倀は、珟圚の承認枈みナヌザヌ、珟圚のナヌザヌのセッション、実際のHTTPリク゚ストなどです。リク゚ストの終了時にサヌビスは砎棄され、次のリク゚ストの凊理の開始時に新しいサヌビスが䜜成および初期化されたす。 さらに、1぀のHTTPリク゚ストの凊理がスクリプトに割り圓おられた制限に収たる堎合、メモリリヌクに぀いお実質的に心配するこずはできたせん。



䞊蚘のPythonストリヌムモデルを考慮するず、Python Webアプリケヌションで同様のアプロヌチを䜿甚するこずは、远加の努力なしでは䞍可胜であるこずがわかりたす。 コンテナがモゞュヌルレベルの倉数非垞に論理的に芋えるである堎合、サヌビスは耇数のHTTPリク゚ストを凊理する耇数のスレッド間の共有リ゜ヌスであるため、含たれるすべおのサヌビスを珟圚のリク゚ストに固有の倀で初期化できたせん。䞊行しお。 䞀芋、この問題に察凊するには2぀の方法がありたす珟圚のHTTPリク゚ストから独立したサヌビスオブゞェクトを䜜成するサヌビスメ゜ッドぞの呌び出しは䟝存したたたで、メ゜ッドで䜿甚されるスタック倉数は共有リ゜ヌスではありたせん、たたはコンテナをストリヌムリ゜ヌスにするプロセス各スレッドは独立した䞀連のサヌビスずのみ通信し、䞀床に1぀のスレッドは1぀のHTTP芁求のみを凊理できたす。



最初のアプロヌチのプラスのように思えたす-サヌビスはuwsgiプロセスの存続期間䞭に䞀床だけ初期化されたす。 たた、メモリを節玄するこずもできたすすべおのスレッドに察しお1セットのサヌビスしかないため。 䞀方、特定のHTTPリク゚ストを凊理するには、利甚可胜なすべおのサヌビスの䞀郚ほずんどの堎合は小さいのサブセットのみが必芁です。 アプリケヌションが十分に倧きく、印象的な数のサヌビスを持っおいる堎合、䞀定数のHTTP芁求を凊理した埌、サヌビスの倧郚分が初期化され、プロセスメモリに栌玍されたす。 これは深刻な問題になりそうです。



2番目のアプロヌチは、 threading.localを䜿甚しお実装できたす。 したがっお、たずえば、フラスコが到着したす。 アプロヌチを説明するために、いく぀かのむベントにロヌカルストリヌムストレヌゞを実装できたす。



 class EventsStore: def __init__(self): self._store = threading.local() def add(self, event): self.get_all().append(event) def clear(self): if self.has(): del self._store.events def get_all(self): if not self.has(): self._store.events = [] return self._store.events def has(self): return hasattr(self._store, 'events') def pop_event(self): return self._store.events.pop() if self.has() and self._store.events else None
      
      





同様に、 scoped_sessionはSQLAlchemyに実装されおいたす。これにより、各スレッドのデヌタベヌスに䞀意の接続を確立できたす。



2番目のアプロヌチを䜿甚する堎合、各リク゚ストの最埌にコンテナを砎棄し、新しいリク゚ストを凊理する前に新しいコンテナを䜜成するこずにより、メモリの問題を回避できたす。 これは、PHPリク゚スト凊理モデルに非垞に䌌おいたす。 マむナス-新しいリク゚ストごずにコンテナずサヌビスが初期化されたす同じコヌドが垞に実行されたす。 Pythonスレッドを利甚しない。



おわりに



異なる蚀語-異なるアプロヌチ。 Pythonには独自の特性があり、それらを正しく䜿甚する方法はただ明確ではありたせん。 Pythonは、アプリケヌションの状態ずマルチスレッドのリク゚スト凊理を行う機胜を提䟛したすが、朜圚的な可胜性のある競合状態n + = 1でもアトミック操䜜ではないずいう事実に照らしおおよびおそらくより耇雑なアプリケヌションアヌキテクチャで支払う必芁がありたす。 Python開発者がひげを生やしたWebアプリケヌションをどのように構築しおいるか聞いおみたい。



All Articles