Tornado用のマルチスレッドアプリケーション





ノンブロッキングWebサーバーTornadoのドキュメントは、負荷にどれだけうまく対処できるかをうまく説明しており、一般的にノンブロッキングサーバーの分野における人類の最高の業績です。 これは部分的に真実です。 しかし、「別のチャット」のフレームワークの外で複雑なアプリケーションを構築するとき、熊手で旅行する前に知っておくべき、多くの明白で微妙な点が明らかになります。 カットの下で、 Trelyazh知的ゲームクラブの開発者は、落とし穴についての考えを共有する準備ができています。



2番目のpythonブランチ、tornado 1.2.1の最新バージョン、およびpsycopg2を介して接続されたpostgresqlについて話していることをすぐに予約してください。



アプリケーションインスタンス



多くのプログラマーは、シングルトンアンチパターンを使用してすばやくアクセスするのが大好きです。

アプリケーションクラスインスタンスに。 さらに水平方向のスケーリングを検討している場合、

これは非常に推奨されません。 リクエストオブジェクトはスレッドセーフをもたらします

あなた自身の足を撃つ危険なしに使用できる銀の大皿のアプリケーション

予想外の場所。



Websocket



ああ、ああ。 最愛のnginxはwebsocketプロトコルをプロキシできません。 「レイ」のファンにとっては、この点で良いニュースもほとんどありません。 多くはha-proxyを称賛しますが、この場合、すべての静的データを正直なnginxを使用して別のノードに転送し、すべての動的コンテンツを竜巻サーバー自体に渡す方が便利でした。 時にはストレスの多い負荷の下での生後6ヶ月は、このソリューションが非常に実行可能であることを示しました。 フラッシュストリップを使用してwsプロトコルをエミュレートする場合、安全でないバージョンへの切り替えを避けるために同じドメインから送信する必要があります。 敷設を伴うソリューションには、ポート843から同じnginxに送信できるフラッシュポリシーxmlも必要です。



データベースへの接続



明らかに、ロードされたサービスでは、くしゃみごとにデータベースとの非常に高価な接続操作について話すことはできません。 psycopg2の最も単純な通常の接続プールを使用することは非常に可能です。 すぐにスレッドセーフなThreadedConnectionPoolを取得します。必要に応じて接続を選択し、リクエストの終了後に忘れずに返してください。 「忘れないで」とは、決して忘れないことを意味します。 内部で発生した例外は何でも。 python finallyコンストラクトを使用するのは適切です。



非同期リクエスト



シングルスレッドのノンブロッキングサーバーでは、なんらかの比較的長いアクションを実行する必要があるまで、すべてが美しく見えます。 レターを送信する、データベースから選択する、外部Webサービスにリクエストを送信するなど。この場合、接続されている他のすべてのクライアントは、ハンドラーの順番が来るまで忠実に待機します。



リクエストハンドラで必要なのがデータベースをプルして何かを表示するだけであれば、 momoko非同期ラッパーを使用できます。 次に、最も単純なクエリは次のようになります。



 class MainHandler(BaseHandler): @tornado.web.asynchronous def get(self): self.db.execute('SELECT 4, 8, 15, 16, 23, 42;', callback=self._on_response) def _on_response(self, cursor): self.write('Query results: %s' % cursor.fetchall()) self.finish()
      
      







安全なマルチスレッド



そのため、ベースおよび外部Webサービスには非同期ツールがあります。 しかし、多くのSQLクエリから大量の作業を行う必要があり、サーバーの腸で面倒なことを数え、さらにディスクI / Oサブシステムをロードする必要がある場合はどうでしょうか。 もちろん、最悪のねじれた伝統の非同期コールバック関数のクラスターを作成することができますが、これはまさに我々が逃げたいものです。



ここでは、標準のスレッドの使用が示唆されているように思えます。 しかし、通常のpythonスレッドを使用すると、負荷がかかった状態でのプロダクションで、ひどいグリッチと悲惨な結果につながります。 はい、はい。 開発者のマシンではすべてが正常に機能します。 通常、そのような場合、プログラマーはGILで祈り始め、可能性と不可能性を必死にロックします。 しかし、問題は、竜巻のすべてがスレッドセーフではないことです。 これを回避するには、httpリクエストをいくつかの段階で処理する必要があります。



  1. tornado .web.asynchronousを使用してget / post関数をデコードします
  2. 要求を受け入れ、入力パラメーターがある場合はそれを確認し、要求インスタンスに保存します
  3. リクエストクラスのメンバー関数からスレッドを実行する
  4. 共有データの変更時にロックを慎重に適用することにより、この関数内ですべての作業を実行します
  5. コールバックを呼び出します。コールバックは、準備済みのデータを使用して、プロセスの最後の_finish()を作成します。


これらの目的のために、小さなMixinを書くことができます:



 class ThreadableMixin: def start_worker(self): threading.Thread(target=self.worker).start() def worker(self): try: self._worker() except tornado.web.HTTPError, e: self.set_status(e.status_code) except: logging.error("_worker problem", exc_info=True) self.set_status(500) tornado.ioloop.IOLoop.instance().add_callback(self.async_callback(self.results)) def results(self): if self.get_status()!=200: self.send_error(self.get_status()) return if hasattr(self, 'res'): self.finish(self.res) return if hasattr(self, 'redir'): self.redirect(self.redir) return self.send_error(500)
      
      







この場合、セキュアなマルチスレッドのリクエスト処理はシンプルでエレガントに見えます:



 class Handler(tornado.web.RequestHandler, ThreadableMixin): def _worker(self): self.res = self.render_string("template.html", title = _("Title"), data = self.application.db.query("select ... where object_id=%s", self.object_id) ) @tornado.web.asynchronous def get(self, object_id): self.object_id = object_id self.start_worker()
      
      







リダイレクトが必要な場合は、_worker()で変数self.redirを目的のURLに設定します。 ajaxリクエストにjsonが必要な場合は、生成されたページの代わりにself.resで、生成されたdictにデータを割り当てます。



もう1つのポイントは、PythonのC拡張機能に関連しています。 呼び出しフローで外部ライブラリを使用する場合は、必ずスレッドセーフステータスを確認してください。



バッチ処理



多くの場合、厳密に定義された時間後に関数を実行する必要があります。 これらは、ユーザータイムアウト、ゲームプロセスの実装、システムメンテナンス手順などです。 これらの目的のために、いわゆる「周期的プロセス」が使用されます。



従来、バッチプロセスを整理するために、Pythonは標準のthreading.Timerを使用します。 私たちがそれを使おうとすると、再び特定の量のとらえどころのない問題が発生します。 これらの目的のために、tornadoはioloop.PeriodicCallbackを提供します。 通常のタイマーの代わりに常に使用してください。 これにより、上記の理由により多くの時間と神経を節約できます。



ローカリゼーションなど



結論として、マルチスレッド処理に関連しないいくつかのヒントを紹介しますが、分岐アプリケーションのパフォーマンスを大幅に向上できる場合があります。



  1. ローカライズに組み込みのトルネードスタブを使用しないでください。 Tornadoは、標準のgettextにバインドする方法を完全に知っており、大量の翻訳でより良い結果を提供します。
  2. 可能なすべてをメモリにキャッシュします。 memcached&co。を忘れてください 必要ありません。 すでに設計プロセスで、アプリケーションを実行するハードウェアプラットフォームを知っている必要があります。 サーバーの余分な数ギガバイトのメモリは、特定のキャッシュ戦略へのアプローチを根本的に変えることができます。
  3. ページ生成時間がシステム内のデータに完全に依存しており、その制限を事前に知ることができない場合は、常にこのリクエストの新しいスレッドを延期してください
  4. 竜巻は非常に高速であるという事実にもかかわらず、この目的のための手段に常に静的を​​与えます。 たとえば、nginx。 FreeBSD / amd64とnginxを搭載したi7 / 16Gb / SASサーバーが静的データを配布する能力を想像することはできません。 物理的に速くなるものはありません。




結果



ストレステスト中、サイトでアクティブに再生されている5000の同時接続(1秒あたり数千のWebSocketメッセージを意味します)は、サーバーが問題なくプルします(LA〜= 0.2、サーバープロセスは8Gbの空きメモリで約400MBを消費します)。 150人の本物のプレイヤーがオンラインで、弾丸を書いて楽しく、サーバーはまったく気付きません(負荷がゼロで、電力が大量に供給されます)。



正面では、次のようになります。







そして、力があなたと共にありますように!



All Articles