2020/10/21
[Python]redis-pyを使ってRedisの便利機能を実装
概要
WebアプリでRedisを以下の用途でよく使います。
- Connection Poolを使う
- JWTトークンで使用済のものをblacklistとして登録する
- Userに特定種類のAPIのリクエストを複数同時に実行したくない場合のチェックに利用する
- DBの負荷が上がってくる場合は、クエリの内容をキャッシュして使用する
- 最悪redisが落ちている場合でもアプリケーションが通常動作するようにする
最近はPythonでバックエンドを実装することが多いのでPythonで対応しました。(Python3.8です)
(もちろんgolangでもphpでも何の言語でも同じです。)
実装内容
redis-pyのインストール
% poetry add redis
# redis = "^3.5.3"がインストールされました
# ↓ pipを使う場合は以下
# pip install redis
Connection Pool
- 以下の
init()
をWebアプリ起動時に実行しておきます。 - 個別のコネクション
conn()
はフレームワークのmiddlewareなどで取得して、http requestのobjectなどに入れておくと便利です
import redis
redis_pool = None
def init():
global redis_pool
# ここら辺は設定値を使うと良さげ
redis_pool = redis.ConnectionPool(
host="127.0.0.1",
port=6379,
db=0,
)
def conn():
return redis.StrictRedis(connection_pool=redis_pool)
JWTトークン使用済の制御
単純なGETおよびSETのみを使用します。
デフォルメすると以下のような感じになります。
- jwt tokenをredisのkeyに含め、jwt tokenの有効期限を使ってredisに格納します。(例えばログアウト時などに使用します)
- userがrequest時に、もしすでにblacklistに登録済のjwt tokenを使っていたら、ログインできないように制御できます
conn: RedisConn # 上記のconnection poolから作成されたredisのコネクション
token = "your-jwt-token-value"
prefix = "jwt_token_blacklist_"
key = f"{prefix}{token}"
ttl = 60 * 60 * 24 * 7 # 7 days (seconds)
# setを呼び出してredisに書き込みます。
conn.set(key, "1", ex=ttl)
# getを呼び出してもしblacklistに登録済なら、403などにします。
flg = conn.get(key)
if flg is not None:
raise YourAuthException("the token can no longer be used.")
リクエストの同時実行制御
- atomicなgetsetを使って処理します
- 実行中の場合は1のフラグをredisに立てて、処理が終ったら削除するようにします
- 不足の自体を考慮して、リクエストの処理が十分に終わると思われる時間でフラグは自動的に消えるようにしておきます
def action:
# 個別処理を実装しておきます
return "OK"
user_id = "your-user-identifier"
prefix = "user_request_block_"
key = f"{prefix}{token}" # pathなどの情報も含めることで、特定のメソッドのAPIだけ同時実行したくないとかもできます
ttl = 10 # リクエストの処理が十分に終わると思われる秒数を設定しておきます
try:
blocked = conn.getset(key, "1")
if blocked == b"1":
raise YourRuntimeException("Could not execute multiple request at the same time.")
conn.expire(key, ttl)
return action()
finally:
conn.delete(key)
こんな感じでしょうか。
今回は、「DBの負荷が上がってくる場合は、クエリの内容をキャッシュして使用する」tipsは詳しく紹介しませんが
redisに格納するkeyのprefixをうまく設計することで
updateやinsertなどのクエリを実行時に、関係するキャッシュをredisから一括で消去(ワイルドカード“*“
を使って)すると便利です。
以上になります。
関連する記事
[Rust]axumとdragonflyを使ったWebsocket Chatのサンプル実装
redis互換のdragonflyをPUBSUBとして利用して、Websocket Chatアプリのサンプル実装を行いました。
[Python]ハイフンなし電話番号からハイフン付きに復元
Pythonでハイフンなしの日本の電話番号をハイフン付きのものに変換する
[Python]BeautifulSoup4でhtmlの解析
BeautifulSoup4というPythonのライブラリを使って、特定のURLのコンテンツを取得し、タイトルや説明文を取得できるようにしました。
[Python]銀行コードと支店コードの取扱
Pythonで銀行コード、支店コードデータを取り扱う便利なライブラリzengin-codeを導入しました。