LoginSignup
13
21

More than 5 years have passed since last update.

PythonでLINE BOT:RQ (Redis Queue)を使った非同期処理

Last updated at Posted at 2017-04-08

LINE BOTをつくったので紹介します。
ソースコードはgithubにあります。

つくったもの

入力された時間(秒)が経過したら通知してくれるLINE BOTです。
面白くないネタですが、非同期処理をやるには丁度よい題材でした。
以下がつくったもののスクリーンショットですが、"180"と入力すると180秒後にメッセージが通知されます。
当然その180秒の間、他のメッセージも処理する必要があります。

s_timer3.PNG

しかしLINEのAPIの仕様上、ユーザーからのリクエストは180秒も待たずにタイムアウトしてしまい、返信できなくなります。
そもそもタイムアウトする前に返信できるならそれが良いですが、
大量の処理が来た場合や、画像処理などどうしても間に合わない重い処理、
今回のタイマーのようなものは一旦リクエストを返した後に処理するしかありません。

基本方針

とりあえずLINE BOTサーバーは他の多くの人がやっているようにheroku上で動かします。
LINE BOTはSSL通信が必須となっていて自分で準備するのはやや手間ですが、
herokuならデフォルトで可能だからです。PaaSの利点ですね。

LINE BOTのアーキテクチャについては以下の記事を参考にしました。

  1. LINE BOTに関する記事
    1.1. 大量メッセージが来ても安心なLINE BOTサーバーのアーキテクチャ

  2. herokuに関する記事
    2.1 Worker Dynos, Background Jobs and Queueing
    2.2 Background Tasks in Python with RQ

非同期処理のためのバックグラウンドワーカーは2.2に従ってRQを使います。
RQを使うためにはRedisサーバーが必要ですが、herokuならRedisToGoというアドオンを追加するだけで良いので簡単です。

$ heroku addons:create redistogo

LINE BOTをRQでやる場合、ユーザーからメッセージを受信したときのMessageEventをざっくり描くと下図のようなシーケンスになります。

  1. LINEサーバーからBOTサーバーにリクエスト(送信者ID、メッセージ、返信用トークン)が届く
  2. 返信用トークンを用いて、LINE APIサーバーにメッセージを返信する(必須ではない)
  3. RQ(ジョブキュー)にメッセージに対するジョブを登録する
  4. LINEサーバーに200を返す
  5. ジョブをジョブキューから取り出す
  6. ジョブを処理する
  7. ジョブの結果をLINE APIサーバーに通知する

ここで、LINE APIサーバーに送信したメッセージがユーザーのLINE画面に表示されます。
重要なのは1~4までの時間がLINE的にもheroku的にも制限されていることです。
ちなみに返信用トークンにも有効期限があります。使うならさっさと使いましょう。
そしてすぐにLINEサーバーに200を返しましょう。
送信者IDが分かっていれば、5~7はいつでも実行できます。(結果の通知が遅すぎるのは問題ですが)

s_be5196d2-02cb-43d3-96f9-c5b8ca806f28.png

今回のLINE BOTでは以下の3つのプロセスが存在します。
リクエストごとにタイマースレッドを作るわけにもいかないので、タイマーはRedisで管理することにしました。

  • LINE BOTサーバー:リクエストを受け付けてRQにジョブを登録する
  • RQワーカー:ジョブを取り出してRedisのソート済みセットにタイマーを登録する(設定時間でソート)
  • Timerワーカー:Redisを定期的に監視し、設定時間になったタイマーがあれば送信者に通知し、タイマーを削除する

Timerワーカーは上のシーケンスには出てこないですが、
RQワーカーはあくまでジョブの登録を契機に実行されるので、タイマーのタイミングは分かりません。
なのでタイマーが設定時間になったかどうかはTimerワーカーが検知してLINE APIサーバーに通知します。

heroku無料枠の制限対策

今回のソースコードですが、このままではherokuの無料枠で動きません。
Procfileにウェブサーバー1つとバックグラウンドワーカー2つを指定していますが、
無料枠では一度に2つのdynoしか設定できないからです。

Procfile
web: gunicorn app:app
rq_worker: python rq_worker.py
timer_worker: python timer_worker.py
$ heroku ps:scale rq_worker=1 timer_worker=1
Scaling dynos... !
 !    Cannot run more than 2 Free size dynos.

このため、残念な対策ですが、同じソースコードをProcfileだけ変更して2つのアプリとしてデプロイする方法を取りました。

LINEBOT用Procfile
web: gunicorn app:app
rq_worker: python rq_worker.py
タイマー用Procfile
timer_worker: python timer_worker.py

この場合、2つのアプリは同じRedisサーバーに接続しなければならないので、どちらかのアプリで作ったRedisToGoアドオンの環境変数REDISTOGO_URLをもう片方のアプリの環境変数にも設定してあげる必要があります。

おまけ

今回作ったLINE BOT、入力に大して何も処理をしてないので数字以外はエラーなります。
s_timer2.PNG

ちなみに入力を形態素解析してゴニョゴニョするLINE BOTも作ってみました( ソースコード:github)。
s_kemo.PNG

13
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
21