在將 python FastAPI 應用程式與 Redis DB 一起使用時,我遇到了一個非常奇怪的問題。最終,我試圖找出方案 2(見下文)失敗的原因。
下面我描述了場景和一些一般的想法。
以下是場景
場景一(作業)
- Redis-db 使用 localhost:6379 在 docker 容器上運行
- Fastapi 在沒有容器的機器上本地運行
- 創建了參考 BaseModel/EmbeddedJsonModel 的簡單 JSONModel
- 運行應用程式時,我可以在日志中看到 fast-api 已連接到 redis
- 如果我發布到 fastapi 應用程式,redis db 會毫無問題地填充
場景 2(非作業)
- Redis-db 使用 localhost:6379 在 docker 容器上運行
- Fastapi 應用程式在與 redis-db 相同的容器 netowkr 上的 docker 容器中運行
- 創建了參考 BaseModel/EmbeddedJsonModel 的簡單 JSONModel
- 當我運行 docker-compose up ...兩個容器都啟動并且我看到快速 api 應用程式已連接到 redis db
- 如果我發布到 fastapi 應用程式,我會收到提示沒有 DB 連接的例外注意:如果我將簡單模型轉換為 HashModel 并洗掉對 BaseModel/EmbeddedJsonModel 的所有參考,則沒有問題
FastApi 代碼
import logging
from typing import Optional, Any
from fastapi import FastAPI, HTTPException, Response, status
from redis_om import Migrator, NotFoundError, get_redis_connection, HashModel, EmbeddedJsonModel, Field, JsonModel
from pydantic import BaseModel, Extra, ValidationError, validator
app = FastAPI(title="FastAPI_Test_App")
log = logging.getLogger(__name__)
log.warning("CONNECTING TO REDIS_HOST: redis-db")
redis_con = get_redis_connection(host="redis-db", decode_responses=True)
class CustomerBase(EmbeddedJsonModel, BaseModel):
first_name: str
last_name: str
email: str
age: int
#CustomerBase.Meta.database = redis_con
log.warning(f"CustomerBase DB connection data: {CustomerBase.Meta.database}") # default database uses localhost
class Customer(JsonModel):
base: CustomerBase
Customer.Meta.database = redis_con # type: ignore
log.warning(f"Customer DB connection data: {Customer.Meta.database}") # this shows redis-db which is CORRECT
@app.get('/customers')
async def all():
customer_pks = Customer.all_pks()
return [format(pk) for pk in customer_pks]
def format(pk: str):
c = Customer.get(pk)
return {
'id': c.pk,
'name': f"{c.first_name} {c.last_name}",
'age': c.age
}
@app.post('/customers')
async def create(customer_base: CustomerBase):
c = Customer(base=customer_base)
#c.Meta.database = redis_con
return c.save()
#return customer.save() # THIS WORKS ALONE...NO OTHER LINES NEED IN THIS FUNCTION
if __name__ == "__main__": # pragma: no cover
log.warning("Running customer app")
# Create a RediSearch index (required for all processes that operate with Tasks)
Migrator().run()
# TODO Move uvicorn to extras_require (if not used in prod)?
import uvicorn
# TODO Add cache in "on event startup"
uvicorn.run("main_REPRODUCE_FAILURE:app", reload=True, host="0.0.0.0", port=5000, log_level="debug")
Dockerfile
FROM python:3.9
RUN apt-get update && apt-get -y install iputils-ping
RUN groupadd -g 1000 appuser && \
useradd -rm -s /bin/bash -d /home/appuser -u 1000 -g 1000 appuser
WORKDIR /home/appuser
RUN python -m pip install --upgrade pip
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
RUN pip install -vvv -e .
EXPOSE 5000
ENTRYPOINT [ "/bin/bash", "-l", "-c" ]
CMD ["python main_REPRODUCE_FAILURE.py"]
碼頭工人組成
version: '3.8'
services:
redis-db:
image: redislabs/rejson:latest
restart: always
command: redis-server --loglevel debug
volumes:
- fastapi-redis-test:/data
ports:
- "6379:6379"
networks:
- fastapi-redis-test
environment:
fastapi-redis-test_ENV: development
fastapi-redis-test:
build:
context: .
dockerfile: Dockerfile
#command: python main.py
image: fastapi-redis-test
depends_on:
- redis-db
ports:
- "5000:5000"
networks:
- fastapi-redis-test
environment:
REDIS_HOST: redis-db
volumes:
- .:/home/appuser
volumes:
fastapi-redis-test:
driver: local
networks:
fastapi-redis-test:
使用 localhost:5000/docs 運行帖子時出現例外
fastapi_redis-fastapi-redis-test-1 | CONNECT TO REDIS_HOST: redis-db
fastapi_redis-fastapi-redis-test-1 | CustomerBase DB connection data: Redis<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
fastapi_redis-fastapi-redis-test-1 | Customer DB connection data: Redis<ConnectionPool<Connection<host=redis-db,port=6379,db=0>>>
fastapi_redis-fastapi-redis-test-1 | CONNECT TO REDIS_HOST: redis-db
fastapi_redis-fastapi-redis-test-1 | CustomerBase DB connection data: Redis<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
fastapi_redis-fastapi-redis-test-1 | Customer DB connection data: Redis<ConnectionPool<Connection<host=redis-db,port=6379,db=0>>>
fastapi_redis-fastapi-redis-test-1 | INFO: Started server process [14]
fastapi_redis-fastapi-redis-test-1 | INFO: Waiting for application startup.
fastapi_redis-fastapi-redis-test-1 | INFO: Application startup complete.
fastapi_redis-redis-db-1 | 1:M 07 Oct 2022 18:08:40.685 - DB 0: 27 keys (0 volatile) in 32 slots HT.
fastapi_redis-redis-db-1 | 1:M 07 Oct 2022 18:08:40.685 . 1 clients connected (0 replicas), 838736 bytes in use
fastapi_redis-fastapi-redis-test-1 | INFO: 172.21.0.1:58606 - "POST /customers HTTP/1.1" 500 Internal Server Error
fastapi_redis-fastapi-redis-test-1 | ERROR: Exception in ASGI application
fastapi_redis-fastapi-redis-test-1 | Traceback (most recent call last):
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis/connection.py", line 611, in connect
fastapi_redis-fastapi-redis-test-1 | sock = self.retry.call_with_retry(
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis/retry.py", line 46, in call_with_retry
fastapi_redis-fastapi-redis-test-1 | return do()
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis/connection.py", line 612, in <lambda>
fastapi_redis-fastapi-redis-test-1 | lambda: self._connect(), lambda error: self.disconnect(error)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis/connection.py", line 677, in _connect
fastapi_redis-fastapi-redis-test-1 | raise err
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis/connection.py", line 665, in _connect
fastapi_redis-fastapi-redis-test-1 | sock.connect(socket_address)
fastapi_redis-fastapi-redis-test-1 | OSError: [Errno 99] Cannot assign requested address
fastapi_redis-fastapi-redis-test-1 |
fastapi_redis-fastapi-redis-test-1 | During handling of the above exception, another exception occurred:
fastapi_redis-fastapi-redis-test-1 |
fastapi_redis-fastapi-redis-test-1 | Traceback (most recent call last):
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 404, in run_asgi
fastapi_redis-fastapi-redis-test-1 | result = await app( # type: ignore[func-returns-value]
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
fastapi_redis-fastapi-redis-test-1 | return await self.app(scope, receive, send)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/fastapi/applications.py", line 270, in __call__
fastapi_redis-fastapi-redis-test-1 | await super().__call__(scope, receive, send)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/starlette/applications.py", line 124, in __call__
fastapi_redis-fastapi-redis-test-1 | await self.middleware_stack(scope, receive, send)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/starlette/middleware/errors.py", line 184, in __call__
fastapi_redis-fastapi-redis-test-1 | raise exc
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/starlette/middleware/errors.py", line 162, in __call__
fastapi_redis-fastapi-redis-test-1 | await self.app(scope, receive, _send)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/starlette/middleware/exceptions.py", line 75, in __call__
fastapi_redis-fastapi-redis-test-1 | raise exc
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/starlette/middleware/exceptions.py", line 64, in __call__
fastapi_redis-fastapi-redis-test-1 | await self.app(scope, receive, sender)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
fastapi_redis-fastapi-redis-test-1 | raise e
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
fastapi_redis-fastapi-redis-test-1 | await self.app(scope, receive, send)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/starlette/routing.py", line 680, in __call__
fastapi_redis-fastapi-redis-test-1 | await route.handle(scope, receive, send)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/starlette/routing.py", line 275, in handle
fastapi_redis-fastapi-redis-test-1 | await self.app(scope, receive, send)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/starlette/routing.py", line 65, in app
fastapi_redis-fastapi-redis-test-1 | response = await func(request)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/fastapi/routing.py", line 221, in app
fastapi_redis-fastapi-redis-test-1 | solved_result = await solve_dependencies(
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 561, in solve_dependencies
fastapi_redis-fastapi-redis-test-1 | ) = await request_body_to_args( # body_params checked above
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/fastapi/dependencies/utils.py", line 696, in request_body_to_args
fastapi_redis-fastapi-redis-test-1 | v_, errors_ = field.validate(value, values, loc=loc)
fastapi_redis-fastapi-redis-test-1 | File "pydantic/fields.py", line 884, in pydantic.fields.ModelField.validate
fastapi_redis-fastapi-redis-test-1 | File "pydantic/fields.py", line 1101, in pydantic.fields.ModelField._validate_singleton
fastapi_redis-fastapi-redis-test-1 | File "pydantic/fields.py", line 1148, in pydantic.fields.ModelField._apply_validators
fastapi_redis-fastapi-redis-test-1 | File "pydantic/class_validators.py", line 318, in pydantic.class_validators._generic_validator_basic.lambda13
fastapi_redis-fastapi-redis-test-1 | File "pydantic/main.py", line 711, in pydantic.main.BaseModel.validate
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis_om/model/model.py", line 1469, in __init__
fastapi_redis-fastapi-redis-test-1 | if not has_redis_json(self.db()):
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis_om/checks.py", line 17, in has_redis_json
fastapi_redis-fastapi-redis-test-1 | command_exists = check_for_command(conn, "json.set")
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis_om/checks.py", line 9, in check_for_command
fastapi_redis-fastapi-redis-test-1 | cmd_info = conn.execute_command("command", "info", cmd)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis/client.py", line 1235, in execute_command
fastapi_redis-fastapi-redis-test-1 | conn = self.connection or pool.get_connection(command_name, **options)
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis/connection.py", line 1387, in get_connection
fastapi_redis-fastapi-redis-test-1 | connection.connect()
fastapi_redis-fastapi-redis-test-1 | File "/usr/local/lib/python3.9/site-packages/redis/connection.py", line 617, in connect
fastapi_redis-fastapi-redis-test-1 | raise ConnectionError(self._error_message(e))
fastapi_redis-fastapi-redis-test-1 | redis.exceptions.ConnectionError: Error 99 connecting to localhost:6379. Cannot assign requested address.
一些故障排除思路:
在 redis 連接期間,我可以看到 fastapi 正確使用容器名稱:redis-db 我發現有趣的是堆疊跟蹤表明在發布期間,它嘗試連接到 localhost,我知道這是不好的 b/c這意味著應用程式正在嘗試連接到我的本地機器而不是 redis 容器。
看到這個錯誤后,我嘗試的一件事是在 CustomerBase 和 Customer 類的 Meta-class 中添加 redis-db 連接。這樣做,我不再得到例外。相反,我得到了 HTTP 422 - 無法處理的物體故障。我認為這只是通過做一些我打破了正常模型的 b/c。所以這讓我想知道......為什么應用程式在發布期間使用 localhost 而不是 redis-db ?或者......還有其他一些奇怪的問題。如前所述,我真的認為 b/c 上還有其他事情……當應用程式不在容器上但 redis 在容器上時,這種失敗的設定作業得很好。
我不確定這是否是實際問題……但這就是我基于 staack 跟蹤所相信的。我愿意接受所有建議:)
任何幫助都會被接受!
uj5u.com熱心網友回復:
這里的問題似乎與redis_om圖書館有關。查看回溯中的最后一行,我們看到redis.exceptions.ConnectionError: Error 99 connecting to localhost:6379. Cannot assign requested address.,即它仍在嘗試連接到 redis on localhost,即使您似乎已將其配置為連接到redis-db。
查看檔案,有兩種配置庫的方法,一種是設定REDIS_OM_URL環境變數(您可以通過REDIS_OM_URL=redis://redis-db:6379在docker-compose.yml檔案中進行設定),或者向您的模型添加一個內部類:
class Customer(JsonModel):
base: CustomerBase
class Meta:
database = redis_con
我猜想在類宣告之后嘗試配置連接,就像你正在做的Customer.Meta.database = redis_con那樣是行不通的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/512582.html
