FastAPI 结合 Redis 使用方法,FastAPI 已经很快了,但是更快的方案依然是结合 Redis 充当缓存

发布时间 2024-01-11 17:05:56作者: 星尘的博客

实现功能或目的

FastAPI 结合 Redis 使用方法,FastAPI 已经很快了,但是 更快的方案依然是继续结合 Redis 来当数据缓存
本文章提供3种 Redis 结合 FastAPI 的方法
分别见这里:

环境:

Windows 10
Python 3.10.11 X64
fastapi==0.108.0
aioredis==2.0.1

第1种方案

使用依赖注入来实现

实现步骤
  1. 安装库
aioredis>=2.0.1
dependency-injector>=4.41.0

结构:

--- Test
   |-- redis.py
   |-- test_router.py
   |-- test_aioredis.py
  1. 初始化Redis连接和关闭
"""
@ File        : redis.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : redis
"""
from typing import AsyncIterator

from aioredis import from_url, Redis


async def init_redis_pool(host: str, password: str, db: int = 0, port: int = 6379) -> AsyncIterator[Redis]:
    session = await from_url(
        url=f"redis://{host}", port=port, password=password, db=db, encoding="utf-8", decode_responses=True)
    yield session
    await session.close()
  1. 添加 server类,实现Redis 动作,Server 依赖于 Redis
"""
@ File        : redis.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : redis
"""
from typing import Awaitable

from aioredis import Redis

class Service(object):
    def __init__(self, redis: Redis) -> None:
        self._redis = redis

    async def set(self, key: str, value: str) -> Awaitable:
        return await self._redis.set(key, value)

    async def get(self, key: str) -> str:
        return await self._redis.get(key)
  1. 创建一个 Container 容器,用于连接Redis并存放连接池
"""
@ File        : redis.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : redis
"""
from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):
    config = providers.Configuration()
    redis_pool = providers.Resource(
        init_redis_pool,
        host=config.redis_host,
        port=config.redis_port,
        db=config.redis_db,
        password=config.redis_password, )
    service = providers.Factory(Service, redis=redis_pool)
  1. 配置路由,实现依赖注入
"""
@ File        : test_router.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_router
"""

from fastapi import Depends, APIRouter
from dependency_injector.wiring import inject, Provide

from lib.redis import Service, Container

router = APIRouter(responses={404: {"message": "Not Found"}})


# 在需要使用缓存的位置,用 inject 装饰,并依赖注入 service: Service = Depends(Provide[Container.service])
@router.get("/router-test")
@inject
async def index(service: Service = Depends(Provide[Container.service])):
    value = await service.set('name', 'value')
    return {"result": value}
  1. 应用程序 APP
"""
@ File        : test_aioredis.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_aioredis
"""
from fastapi import FastAPI, Depends
from dependency_injector.wiring import inject, Provide

from redis import Service, Container
import test_router

app = FastAPI()
app.include_router(router=test_router.router)


# 在需要使用缓存的位置,用 inject 装饰,并依赖注入 service: Service = Depends(Provide[Container.service])
@app.get("/test")
@inject
async def index(service: Service = Depends(Provide[Container.service])):
    value = await service.set('name', 'value')
    return {"result": value}


# 创建容器
container = Container()
container.config.redis_host.from_value("localhost")
container.config.redis_password.from_env("REDIS_PASSWORD", "<YOU PASSWORD>") # 若环境变量不存在,则使用 <YOU PASSWORD>
container.config.redis_db.from_value(0)
container.config.redis_port.from_value(6379)

# 创建容器时,要将需要依赖注入的模块加载到容器中,否则将无法调用依赖项,报属性错误
container.wire(modules=[__name__, test_router.__name__])


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app='test_aioredis:app', host="127.0.0.1", port=8000, reload=True)

第2种方案

使用Lifespan Events生命周期事件来实现

实现步骤
  1. 安装库
aioredis>=2.0.1
  1. 初始化Redis连接和关闭
    结构:
--- Test
   |-- test_router2.py
   |-- test_aioredis2.py
"""
@ File        : test_aioredis2.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_aioredis2
"""
from typing import AsyncIterator

from aioredis import from_url, Redis


async def init_redis_pool(host: str, password: str, db: int = 0, port: int = 6379) -> AsyncIterator[Redis]:
    session = await from_url(
        url=f"redis://{host}", port=port, password=password, db=db, encoding="utf-8", decode_responses=True)
    return session
  1. 创建异步上下文管理器
"""
@ File        : test_aioredis2.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_aioredis2
"""
from contextlib import asynccontextmanager

from fastapi import FastAPI


# 关于生命周期事件详见:https://fastapi.tiangolo.com/zh/advanced/events/#lifespan
@asynccontextmanager
async def lifespan(apps: FastAPI):
    session = await init_redis_pool(host="127.0.0.1", password="<YOU PASSWORD>", db=0, port=6379) # 你的密码<YOU PASSWORD>
    # 将Redis连接添加到app全局实例,详见:https://www.starlette.io/applications/
    # Storing state on the app instance
    app.state.redis = session
    yield
    await session.close()
  1. 在路由中使用APP全局实例,调用Redis
"""
@ File        : test_router2.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_router2
"""

from fastapi import APIRouter, Request

router = APIRouter(responses={404: {"message": "Not Found"}})


# 利用 request 访问全局实例,若 request 可用,则request.app可用
@router.get("/router-test")
async def index(request: Request):
    value = await request.app.state.redis.set('name', 'value')
    return {"result": value}

  1. 使用 异步上下文管理器,添加到应用 APP
"""
@ File        : test_aioredis2.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_aioredis2
"""
from fastapi import Request

import test_router2


app = FastAPI(lifespan=lifespan) # 添加生命周期为函数 lifespan
app.include_router(router=test_router2.router)


# 利用 request 访问全局实例,若 request 可用,则request.app可用
@app.get("/test")
async def index(request: Request):
    value = await request.app.state.redis.set('name', 'value')
    return {"result": value}


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app='test_aioredis2:app', host="127.0.0.1", port=8000, reload=True)

第3种方案

使用startup、shutdown事件来实现(不推荐,因为被Lifespan Events生命周期事件所取代)

实现步骤
  1. 安装库
aioredis>=2.0.1
  1. 初始化Redis连接和关闭
    结构:
--- Test
   |-- test_router3.py
   |-- test_aioredis3.py
"""
@ File        : test_aioredis3.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_aioredis3
"""
from typing import AsyncIterator

from aioredis import from_url, Redis


async def init_redis_pool(host: str, password: str, db: int = 0, port: int = 6379) -> AsyncIterator[Redis]:
    session = await from_url(
        url=f"redis://{host}", port=port, password=password, db=db, encoding="utf-8", decode_responses=True)
    return session
  1. 创建 startup、shutdown事件
"""
@ File        : test_aioredis3.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_aioredis3
"""
from fastapi import FastAPI


@app.on_event("startup")
async def startup_event():
    # 将Redis连接添加到app全局实例,详见:https://www.starlette.io/applications/
    # Storing state on the app instance
    # 你的密码<YOU PASSWORD>
    app.state.redis = await init_redis_pool(host="127.0.0.1", password="<YOU PASSWORD>", db=0, port=6379)


@app.on_event("shutdown")
async def shutdown_event():
    await app.state.redis.close()
  1. 在路由中使用APP全局实例,调用Redis
"""
@ File        : test_router3.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_router3
"""

from fastapi import APIRouter, Request

router = APIRouter(responses={404: {"message": "Not Found"}})


# 利用 request 访问全局实例,若 request 可用,则request.app可用
@router.get("/router-test")
async def index(request: Request):
    value = await request.app.state.redis.set('name', 'value')
    return {"result": value}

  1. 应用 APP,直接使用request 访问全局实例
"""
@ File        : test_aioredis3.py
@ Author      : yqbao
@ Version     : V1.0.0
@ Description : test_aioredis3
"""
from fastapi import Request

import test_router3


app = FastAPI()
app.include_router(router=test_router3.router)


# 利用 request 访问全局实例,若 request 可用,则request.app可用
@app.get("/test")
async def index(request: Request):
    value = await request.app.state.redis.set('name', 'value')
    return {"result": value}


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app='test_aioredis3:app', host="127.0.0.1", port=8000, reload=True)

本文章的原文地址
GitHub主页