Redis to Goがadd onから削除されたことによってしばらく混乱していたのですが、やっとすっきり解決したので備忘録として公開。
何が書いてあるか
- Heroku上のPythonからHeroku Data for Redisを使うための方法が書いてある
- Macのローカルで同じ環境を実現して開発する方法が書いてある
Let's go
HerokuのPythonからバックグラウンドタスクを処理しようとする場合のオフィシャルドキュメントはこちら
ただし、これはRedis to Goがadd_onに存在していたときの情報で、若干変わっているので英語版を見る必要がある。
そして、worker.py にはこう書けと書いてあるのだが、それだと動かない
redis_url = os.getenv('REDIS_URL', 'redis://localhost:6379')
conn = redis.from_url(redis_url)
Heroku Data for Redisのプランがhobbyの場合は問題ないのだが、課金すると突然動かなくなる。
なぜかというとTLSが必要になるから。
これについてはここに書いてある
import os
from urllib.parse import urlparse
import redis
url = urlparse(os.environ.get("REDIS_URL"))
r = redis.Redis(host=url.hostname, port=url.port, username=url.username, password=url.password, ssl=True, ssl_cert_reqs=None)
そして、何故かこのままだと動かない。urlparseでportがうまく認識されないっぽい。謎。
ということで、最終的にはhost,port,passを環境変数に入れることになった。
上記の原因はusernameが含まれていたからだった。
Heroku Data for Redisを追加してそのURLを見ると分かるのだがusernameは存在しないのだ。
ということで、以下の様にするのが正解であった。元々はhost,pass,portをそれぞれ環境変数に入れてそれを使う形にしていたのだが、Heroku Data for Redisは定期的にURLが変更される。そのタイミングで使えなくなってしまうので、設定は以下のようにしましょう。
2023.1.12追記 ドキュメントページに修正を反映してもらいました
https://devcenter.heroku.com/articles/connecting-heroku-redis#connecting-in-python
import os
import redis
from urllib.parse import urlparse
from rq import Worker, Queue, Connection
import logging
logger = logging.getLogger(__name__)
listen = ["events", 'cursor', 'high', 'default', 'low']
if os.environ.get("ENV") != 'production':
print("development")
conn = redis.Redis(host='localhost', port=6379, db=0)
else:
url = urlparse(os.environ.get("REDIS_URL"))
conn = redis.Redis(
host=url.hostname,
port=url.port,
password=url.password,
ssl=True,
ssl_cert_reqs=None
)
if __name__ == '__main__':
with Connection(conn):
print("worker start")
worker = Worker(map(Queue, listen))
worker.work()
これらをHerokuの環境変数に入れておく
export REDIS_HOST=hostのURL
export REDIS_PORT=REDISのポート番号
export REDIS_PASS=REDISのパスワード
if os.environ.get("ENV") != 'production':
これは、ローカル開発環境で使うための分岐。
MacにRedisインストールして、ターミナルから
$ redis-server
するとRedisが立ち上がる。
ここに接続する場合は、環境変数ENVを
export ENV=local
としておけば
conn = redis.Redis(host='localhost', port=6379, db=0)
を使ってローカルのRedisに接続される
workerについて
作ったworker.pyをどこに置くかというと、ドキュメントルートである。
ProcFileにはこんなふうに書く
worker: python ./worker.py
実は、かねてよりここに書いてある形でSlack用のアプリケーションを書いている
ここの構成では、Pythonで動く部分は
/backend/
以下にファイルを配置しており、worker.pyももれなくそれに従う形で配置していた
/backend/worker.py
そして、ProcFileには
worker: python ./backend/worker.py
と書いていたのだけど、これだとパスが通らない。workerはドキュメントルートに配置しましょう。そうしないと、workerから呼び出す関数へのパスがおかしくなるので実行できないのです。
2種類のworkerを使いたい
処理が異なる2つのバックグラウンドタスクが存在しており、片方が割と重たいけど常に使われているものではない、且つもう一方は常時稼働が必要なタスクなのでそちらに影響を与えたくないということで2つのworkerを設置することにしました。
最初一つのworkerでやろうとしたら、重たい方を回したら、常時稼働が必要な方が止まりました。という訳で分離しましょうということにしました。
- workerのファイルをコピーして若干変更
- Redisももう一つadd on追加して2つ起動するように変更
import os
import redis
from urllib.parse import urlparse
from rq import Worker, Queue, Connection
import logging
logger = logging.getLogger(__name__)
listen = ["events", 'cursor', 'high', 'default', 'low']
if os.environ.get("ENV") != 'production':
print("development")
conn_c = redis.Redis(host='localhost', port=6379, db=0)
else:
url = urlparse(os.environ.get(os.environ.get("REDIS_COLLECTOR_KEY")))
conn_c = redis.Redis(
host=url.hostname,
port=url.port,
password=url.password,
ssl=True,
ssl_cert_reqs=None
)
if __name__ == '__main__':
with Connection(conn_c):
worker = Worker(map(Queue, listen))
worker.work()
ほぼ同じですが、Redisの接続先をもう一つの方に変更しています。
環境変数を追加しておきます
- REDIS_C_HOST
- REDIS_C_PORT
- REDIS_C_PASS
ProcFileにも一行追加しています
worker: python ./worker.py
collect: python ./workerc.py
pythonから呼び出す形はこんな感じ
from rq import Queue
from worker import conn
from workerc import conn_c
from .collect import start_collect
q = Queue(connection=conn,default_timeout=100)
c = Queue(connection=conn_c,default_timeout=1000000000)
@bolt_app.view("collect_past_posts_submit")
def collect_past_posts_submit(ack, body, logger, client, view, context):
ack()
result = c.enqueue(start_collect, body['team']['id'], context['user_id'])
@bolt_app.event("message")
def handle_message_events(client, body, logger, context):
user_id = context['user_id']
try:
userinfo = client.users_info(
user=user_id,
include_locale=True
)
except SlackApiError as e:
print("Error fetching conversations: {}".format(e))
post = q.enqueue(handleMessage,client,body,context,logger,userinfo,job_timeout=36000)
def handleMessage(client,body,context,logger,userinfo):
print('handle_message_events ■□■□■□■□■□■□■□■□■□■□■□')
#何かしらの処理----
ローカルでRedis2つ立ち上げる方法までは調べなかったので、ローカルでも同じ環境にしたいという場合はもう少し調べる必要があります。
一つのRedisに2つのworkerで接続しようとすると、処理が2つに別れないですが、検証にはそれほど困らないと思う。
Herokuのworkerのログを見るには?
以下のコマンドで見ることができます
heroku logs -t -d worker -a アプリ名
2つ目の方は
heroku logs -t -d collect -a アプリ名
となります。ProcFileで指定した名前で呼び出す形式です。
heroku logs -t -a アプリ名
とすると、アプリ内の全dynoのログが確認できます。
処理がきちんと指定したworkerに伝わっているかどうかを確認するためには-d オプションを付けてworkerのログを見る必要があります。
コンソールから接続するには
heroku redis:cli -a アプリ名
という形で接続します。
一つのアプリに複数のREDISインスタンスを紐付けている場合だとこれだと接続できませんので
heroku redis:cli -a アプリ名 HEROKU_REDIS_xxxx
というように、REDISを追加した際につけられた名前を指定してアクセスしましょう
Redisにログインして作業する
keyを確認するには
>keys *
これですべてのキーを確認できます。
キーを削除する場合
>flushdb
TTLが設定されていないキーの取得
>info keyspace
以上
開発してるSlackアプリの紹介
よろしければインストールして使ってみて下さい!こちらのTIPSを利用しているのはTimeLineです。
- TASUKARU-TaskALL- https://lne.st/3x1u
- TimeLine for Slack https://lne.st/wr21
- OYASUMI bot https://lne.st/oyasumilink
- TIPS https://lne.st/tips
- YOKOKU for Slack https://lne.st/4cdy
TASUKARU
Slackで色々な人からメンションをもらって混乱したことはありませんか?
何か頼まれていたはずだけど思い出せない… そんな状態からあなたを救います。
TASUKARUはSlack専用のタスクマネージャーです。
デフォルトのスレッドやメンション画面では、処理が終わったpostをアーカイブすることが出来ませんが、TASUKARUならそれが出来ます。
今アクションが必要なものだけに集中することができる、それがTASUKARUです。 https://lne.st/3x1u
TimeLine for Slack
全ての公開チャンネルのpostを一つのチャンネルにまとめるタイムラインチャンネルを生成するアプリです。
一部のチャンネルのみを集めたミックスチャンネルを作成すると、自分が必要なチャンネルだけのタイムラインを作り出すことが出来ます。
オプション機能として、メッセージ転送時にDeepL翻訳を挟むことが出来ます。 https://lne.st/wr21
OYASUMI bot
Googleカレンダーのスケジュールに「休み」等の、休暇判定キーワードが入っていた場合に、自動的にSlackをスヌーズ状態に変更します。
メンションがあった場合に、メンションした相手にその日は休暇ですというレスを付けます。
スタッフの休暇を気持ちよく過ごしてもらう為のアプリです。 https://lne.st/oyasumilink
TIPS
Slack用のリマインダーアプリです。
デフォルト機能のリマインダーは、一つのスケジュールに一つのメッセージの設定しか出来ません。
このアプリでは、一つのスケジュールに複数のメッセージを登録し、ランダムで指定された時間にpostします。
チャンネル特有のノウハウ等をスケジュール登録しておくことで、自然と情報が浸透する状態を作ります。 https://lne.st/tips
YOKOKU for Slack
OYASUMI botの応用です。
例えば「営業」というキーワードが入った予定だけを抽出したいと思ったことはありませんか?
カレンダーをいちいち検索するのは手間がかかります。
このアプリを用いれば、特定のキーワードが入った予定を、特定のチャンネルに流すことが出来ます。 https://lne.st/4cdy